php实现协程,真正的异步
分类:
版权声明:本文为博主原创文章,未经博主允许不得转载。
github上php的协程大部分是根据这篇文章实现的:http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html。
它们最终的结果都是把回调变成了优雅的顺序执行的代码,但还是阻塞的,不是真正的异步。
比如最热门的:https://github.com/recoilphp/recoil
先安装:
- composer require recoil/recoil
执行:
- <?php
- //recoil.php
- include __DIR__ . '/vendor/autoload.php';
- use Recoil\React\ReactKernel;
- $i = 100000;
- ReactKernel::start(task1());
- ReactKernel::start(task2());
- function task1(){
- global $i;
- echo "wait start" . PHP_EOL;
- while ($i-- > 0) {
- yield;
- }
- echo "wait end" . PHP_EOL;
- };
- function task2(){
- echo "Hello " . PHP_EOL;
- yield;
- echo "world!" . PHP_EOL;
- }
结果:
wait start
//等待若干秒
wait end
Hello
world!
我本来是想让两个任务并行,结果两个任务变成了串行,中间等待的时间什么事情都干不了。React响应式的编程是严格禁止这种等待的,所以我就参照unity3d的协程自己写了个php版本的。上代码:
- <?php
- //Coroutine.php
- //依赖swoole实现的定时器,也可以用其它方法实现定时器
- class Coroutine
- {
- //可以根据需要更改定时器间隔,单位ms
- const TICK_INTERVAL = 1;
- private $routineList;
- private $tickId = -1;
- public function __construct()
- {
- $this->routineList = [];
- }
- public function start(Generator $routine)
- {
- $task = new Task($routine);
- $this->routineList[] = $task;
- $this->startTick();
- }
- public function stop(Generator $routine)
- {
- foreach ($this->routineList as $k => $task) {
- if($task->getRoutine() == $routine){
- unset($this->routineList[$k]);
- }
- }
- }
- private function startTick()
- {
- swoole_timer_tick(self::TICK_INTERVAL, function($timerId){
- $this->tickId = $timerId;
- $this->run();
- });
- }
- private function stopTick()
- {
- if($this->tickId >= 0) {
- swoole_timer_clear($this->tickId);
- }
- }
- private function run()
- {
- if(empty($this->routineList)){
- $this->stopTick();
- return;
- }
- foreach ($this->routineList as $k => $task) {
- $task->run();
- if($task->isFinished()){
- unset($this->routineList[$k]);
- }
- }
- }
- }
- class Task
- {
- protected $stack;
- protected $routine;
- public function __construct(Generator $routine)
- {
- $this->routine = $routine;
- $this->stack = new SplStack();
- }
- /**
- * [run 协程调度]
- * @return [type] [description]
- */
- public function run()
- {
- $routine = &$this->routine;
- try {
- if(!$routine){
- return;
- }
- $value = $routine->current();
- //嵌套的协程
- if ($value instanceof Generator) {
- $this->stack->push($routine);
- $routine = $value;
- return;
- }
- //嵌套的协程返回
- if(!$routine->valid() && !$this->stack->isEmpty()) {
- $routine = $this->stack->pop();
- }
- $routine->next();
- } catch (Exception $e) {
- if ($this->stack->isEmpty()) {
- /*
- throw the exception
- */
- return;
- }
- }
- }
- /**
- * [isFinished 判断该task是否完成]
- * @return boolean [description]
- */
- public function isFinished()
- {
- return $this->stack->isEmpty() && !$this->routine->valid();
- }
- public function getRoutine()
- {
- return $this->routine;
- }
- }
测试代码:
- <?php
- //test.php
- require 'Coroutine.php';
- $i = 10000;
- $c = new Coroutine();
- $c->start(task1());
- $c->start(task2());
- function task1(){
- global $i;
- echo "wait start" . PHP_EOL;
- while ($i-- > 0) {
- yield;
- }
- echo "wait end" . PHP_EOL;
- };
- function task2(){
- echo "Hello " . PHP_EOL;
- yield;
- echo "world!" . PHP_EOL;
- }
结果:
wait start
Hello
world!
//等待几秒,但不阻塞
wait end