美文网首页程序员
zan框架入门(一)——协程

zan框架入门(一)——协程

作者: 小小浪把_Dont_know拍 | 来源:发表于2017-01-27 09:00 被阅读280次

    zan 是基于PHP协程的网络服务框架,要使用zan框架,首先需要了解php的yield。建议先看一下 在PHP中使用协程实现多任务调度 这篇文章,基础部分内容都只是这篇文章的整理。

    yield

    yield是php的生成器语法,放在函数内部使用,其效果是return值并且中断此函数的执行。

    一个简单的生成值的例子

    <?php
    function gen_one_to_three() {
        for ($i = 1; $i <= 3; $i++) {
            //注意变量$i的值在不同的yield之间是保持传递的。
            yield $i;
        }
    }
    
    $generator = gen_one_to_three();
    foreach ($generator as $value) {
        echo "$value\n";
    }
    ?>
    

    以上例程会输出

    1
    2
    3
    

    Generator

    一个方法如果内部有yield语法,那么调用这个方法,就会返回一个Generator的对象

    <?php
    function gen_one_to_three() {
        for ($i = 1; $i <= 3; $i++) {
            //注意变量$i的值在不同的yield之间是保持传递的。
            yield $i;
        }
    }
    
    $generator = gen_one_to_three();
    var_dump($generator);
    ?>
    

    以上例程会输出

    class Generator#1 (0) {
    }
    

    查看php的官方手册,Generator的类摘要如下

    Generator implements Iterator {
    /* 方法 */
    public mixed current ( void )
    public mixed key ( void )
    public void next ( void )
    public void rewind ( void )
    public mixed send ( mixed $value )
    public void throw ( Exception $exception )
    public bool valid ( void )
    public void __wakeup ( void )
    }
    

    Iterator

    实现了Iterator接口的类,都可以用foreach循环来遍历

    Iterator extends Traversable {
    /* 方法 */
    abstract public mixed current ( void )
    abstract public scalar key ( void )
    abstract public void next ( void )
    abstract public void rewind ( void )
    abstract public boolean valid ( void )
    }
    

    协程

    协程的支持是在迭代生成器的基础上, 增加了可以回送数据给生成器的功能。这就把生成器到调用者的单向通信转变为两者之间的双向通信。下面这个例子说明了如何同时进行接收和发送。

    <?php
    function gen() {
        $ret = (yield 'yield1');
        var_dump($ret);
        $ret = (yield 'yield2');
        var_dump($ret);
    }
     
    $gen = gen();
    var_dump($gen->current());    // string(6) "yield1"
    var_dump($gen->send('ret1')); // string(4) "ret1"   (the first var_dump in gen)
                                  // string(6) "yield2" (the var_dump of the ->send() return value)
    var_dump($gen->send('ret2')); // string(4) "ret2"   (again from within gen)
                                  // NULL               (the return value of ->send())
    ?>
    

    这里段代码看起来可能有点费解,调用send之前为什么需要先调用一次current,这个和send的机制有关

    Generator::send 『如果当这个方法被调用时,生成器不在 yield 表达式,那么在传入值之前,它会先运行到第一个 yield 表达式。』,接着『向生成器中传入一个值,并且当做 yield 表达式的结果,然后继续执行生成器。』
    

    多任务协作

    了解了协程以后,我们就可以基于协程来实现多任务协作了。
    多任务协作,有三个比较核心的概念:Task、Coroutine、Scheduler。

    Task

    首先看一下轻量级的task代码

    <?php
    class Task {
        protected $taskId;
        protected $coroutine;
        protected $sendValue = null;
        protected $beforeFirstYield = true;
     
        public function __construct($taskId, Generator $coroutine) {
            $this->taskId = $taskId;
            $this->coroutine = $coroutine;
        }
     
        public function getTaskId() {
            return $this->taskId;
        }
     
        public function setSendValue($sendValue) {
            $this->sendValue = $sendValue;
        }
     
        public function run() {
            if ($this->beforeFirstYield) {
                $this->beforeFirstYield = false;
                return $this->coroutine->current();
            } else {
                $retval = $this->coroutine->send($this->sendValue);
                $this->sendValue = null;
                return $retval;
            }
        }
     
        public function isFinished() {
            return !$this->coroutine->valid();
        }
    }
    

    这是Task最简单的版本,只处理单个的yield方法。一个Task处理一个协程。

    Scheduler

    Scheduler就是调度器。顾名思义,就是调度任务用的。上代码:

    <?php
    class Scheduler {
        protected $maxTaskId = 0;
        protected $taskMap = []; // taskId => task
        protected $taskQueue;
     
        public function __construct() {
            $this->taskQueue = new SplQueue();
        }
     
        public function newTask(Generator $coroutine) {
            $tid = ++$this->maxTaskId;
            $task = new Task($tid, $coroutine);
            $this->taskMap[$tid] = $task;
            $this->schedule($task);
            return $tid;
        }
     
        public function schedule(Task $task) {
            $this->taskQueue->enqueue($task);
        }
     
        public function run() {
            while (!$this->taskQueue->isEmpty()) {
                $task = $this->taskQueue->dequeue();
                $task->run();
     
                if ($task->isFinished()) {
                    unset($this->taskMap[$task->getTaskId()]);
                } else {
                    $this->schedule($task);
                }
            }
        }
    }
    ?>
    

    其实很简单,就是用队列实现了任务调度的功能而已。

    参考资料

    zan框架仓库
    PHP: 生成器语法- Manual
    PHP: 生成器 - Manual
    Iterator(迭代器)接口
    PHP SPL笔记
    在PHP中使用协程实现多任务调度

    相关文章

      网友评论

        本文标题:zan框架入门(一)——协程

        本文链接:https://www.haomeiwen.com/subject/vbyfittx.html