Implementation of Actor concurrency model with Swoole under PHP
- 2021-12-12 03:54:17
- OfStack
What is Actor?
Actor for PHPer, It may be unfamiliar. Students who have written Java will be familiar with it. Java1 is directly wired (although PHP has Pthread, it is not popular). It is a concurrent model without shared memory. The data in each Actor exists independently, and Actor is interactively scheduled in the form of message passing. Actor is a highly abstract programming model, which is very suitable for games and hardware industries.
Swoole Synergy and Mailbox
Thanks to Swoole4.x, we can quickly realize one mailbox mode scheduling based on Swoole and Channel. The simulation code is as follows:
use Swoole\Coroutine\Channel;
go(function (){
// Create 10 Mailbox channel
$mailBoxes = [];
for ($i = 1;$i <= 10;$i++){
$mailBoxes[$i] = new Channel(16);
}
// Simulation master Post office dispatching , Random image 1 Deliver messages in a mailbox
go(function ()use($mailBoxes){
while (1){
\co::sleep(2);
$key = rand(1,10);
($mailBoxes[$key])->push(time());
}
});
// Simulation actor Entity consumption
for ($i = 1;$i <= 10;$i++){
go(function ()use($mailBoxes,$i){
while (1){
$msg = ($mailBoxes[$i])->pop();
echo "Actor {$i} recv msg : {$msg} \n";
}
});
}
});
The above code executes the output:
php test.php
Actor 8 recv msg : 1559622691
Actor 10 recv msg : 1559622693
Actor 1 recv msg : 1559622695
Actor 5 recv msg : 1559622697
When there is no data in POP, the co-process channel will automatically give up the execution right (see Swoole co-process scheduling for details)
Actor Library
Based on the above principle, we implement a co-process Actor library with multi-process distribution
composer require easyswoole/actor=2.x-dev
We rely on the dev library for testing, and production can rely on the stable version itself
Process relationship
In Actor model of Easyswoole, there are two groups of processes, one is proxy process, which is used to realize Actor external service, and the other is worker process. proxy process and worker process communicate through unixsock, while Actor instances are evenly distributed among worker.
Sample code
For example, in a chat room, we can define a room model.
namespace EasySwoole\Actor\Test;
use EasySwoole\Actor\AbstractActor;
use EasySwoole\Actor\ActorConfig;
class RoomActor extends AbstractActor
{
public static function configure(ActorConfig $actorConfig)
{
$actorConfig->setActorName('Room');
}
public function onStart()
{
// Whenever 1 A RoomActor This callback is executed when the entity is created
var_dump('room actor '.$this->actorId().' start');
}
public function onMessage($msg)
{
// Whenever 1 A RoomActor When an entity receives an external message, it executes this callback when
var_dump('room actor '.$this->actorId().' onmessage: '.$msg);
return 'reply at '.time();
}
public function onExit($arg)
{
// Whenever 1 A RoomActor This callback is executed when the entity exits
var_dump('room actor '.$this->actorId().' exit at arg: '.$arg);
return 'exit at '.time();
}
protected function onException(\Throwable $throwable)
{
// Whenever 1 A RoomActor This callback is executed when an exception occurs
var_dump($throwable->getMessage());
}
}
Create an Actor service in cli mode
use EasySwoole\Actor\Actor;
use EasySwoole\Actor\Test\RoomActor;
use EasySwoole\Actor\ProxyProcess;
Actor::getInstance()->register(RoomActor::class);
$list = Actor::getInstance()->generateProcess();
foreach ($list['proxy'] as $proxy){
/** @var ProxyProcess $proxy */
$proxy->getProcess()->start();
}
foreach ($list['worker'] as $actors){
foreach ($actors as $actorProcess){
/** @var ProxyProcess $actorProcess */
$actorProcess->getProcess()->start();
}
}
while($ret = \Swoole\Process::wait()) {
echo "PID={$ret['pid']}\n";
}
Create an cli test script
use EasySwoole\Actor\Actor;
use EasySwoole\Actor\Test\RoomActor;
Actor::getInstance()->register(RoomActor::class);
go(function (){
$actorId = RoomActor::client()->create('create arg1');
var_dump($actorId);
\co::sleep(3);
var_dump(RoomActor::client()->send($actorId,'this is msg'));
\co::sleep(3);
var_dump(RoomActor::client()->exit($actorId,'this is exit arg'));
\co::sleep(3);
RoomActor::client()->create('create arg2');
\co::sleep(3);
RoomActor::client()->create('create arg3');
\co::sleep(3);
var_dump(RoomActor::client()->sendAll('sendAll msg'));
\co::sleep(3);
var_dump(RoomActor::client()->status());
\co::sleep(3);
var_dump(RoomActor::client()->exitAll('sendAll exit'));
});
The execution results of the above code are as follows:
Server side
php test.php
string(40) "room actor 00101000000000000000001 start"
string(57) "room actor 00101000000000000000001 onmessage: this is msg"
string(64) "room actor 00101000000000000000001 exit at arg: this is exit arg"
string(40) "room actor 00101000000000000000002 start"
string(40) "room actor 00103000000000000000001 start"
string(57) "room actor 00101000000000000000002 onmessage: sendAll msg"
string(57) "room actor 00103000000000000000001 onmessage: sendAll msg"
string(60) "room actor 00101000000000000000002 exit at arg: sendAll exit"
string(60) "room actor 00103000000000000000001 exit at arg: sendAll exit"
Client
php test2.php
string(23) "00101000000000000000001"
string(19) "reply at 1559623925"
string(18) "exit at 1559623928"
bool(true)
array(3) {
[1]=>
int(1)
[2]=>
int(0)
[3]=>
int(1)
}
bool(true)
More details can be obtained from the document support of EasySwoole project official website http://easyswoole.com/
If you like the EasySwoole project, you can give an star https://github.com/easy-swoole/easyswoole