SoFunction
Updated on 2025-03-06

Methods for implementing Actor concurrency model using Swoole under PHP

What is an Actor?

Actors may be relatively unfamiliar to PHPer. Students who have written Java will be more familiar with it. Java has always had the concept of threads (although PHP has Pthread, but it is not popular). It is a concurrency model with non-shared memory. The data in each Actor exists independently. The interactive scheduling between Actors is carried out through message delivery. Actors are a highly abstract programming model, which is very suitable for the gaming and hardware industries.

Swoole coroutine and mailbox

Thanks to this, we can quickly implement a mailbox mode scheduling based on Swoole's coroutine and Channel. The simulation code is as follows:

use Swoole\Coroutine\Channel;
go(function (){
  //Create ten mailbox channels  $mailBoxes = [];
  for ($i = 1;$i <= 10;$i++){
    $mailBoxes[$i] = new Channel(16);
  }
  //Simulate master post office scheduling, randomly deliver messages like a mailbox  go(function ()use($mailBoxes){
    while (1){
      \co::sleep(2);
      $key = rand(1,10);
      ($mailBoxes[$key])->push(time());
    }
  });
  //Simulate 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
Actor 8 recv msg : 1559622691
Actor 10 recv msg : 1559622693
Actor 1 recv msg : 1559622695
Actor 5 recv msg : 1559622697

Every time the coroutine channel encounters no data in POP, it will automatically give up the execution rights (you can check Swoole coroutine scheduling for details)

Actor Library

Based on the above principle, we implemented a multi-process distributed coroutine Actor library

composer require easyswoole/actor=-dev

We rely on the dev library for testing, and production can rely on the stable version by itself

Process Relationship

In the Easyswoole Actor model, there are two groups of processes, one is a proxy process, which is used to implement the Actor external service, and the other is a worker process. The proxy process and the worker process communicate through unixsock, and the Actor instances are evenly distributed in the 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 a RoomActor entity is created, the callback will be executed    var_dump('room actor '.$this->actorId().' start');
  }
  public function onMessage($msg)
  {
    // Whenever a RoomActor entity receives an external message, the callback will be executed.    var_dump('room actor '.$this->actorId().' onmessage: '.$msg);
    return 'reply at '.time();
  }
  public function onExit($arg)
  { 
    // Whenever a RoomActor entity exits, the callback will be executed    var_dump('room actor '.$this->actorId().' exit at arg: '.$arg);
    return 'exit at '.time();
  }
  protected function onException(\Throwable $throwable)
  {
    //Whenever an exception occurs in a RoomActor, the callback will be executed    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 a 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 above code execution results are as follows:

Server side

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  
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 on the EasySwoole project official website/

If you like EasySwoole project, you can give it a star/easy-swoole/easyswoole

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.