4

php实现协程,真正的异步

 3 years ago
source link: https://blog.p2hp.com/archives/4275
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
php实现协程,真正的异步 | Lenix Blog

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

注:此文章需要验证。

来自 http://m.blog.csdn.net/article/details?id=51684210


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK