BeWithYou

胡搞的技术博客

  1. 首页
  2. PHP
  3. PHP中的协程(一)

PHP中的协程(一)


之前学习Lua的时候第一次接触到了协程(coroutine)的概念。而PHP5.5版本中也加入了协程的概念,从此PHP编程又有了新的思路和玩法。这里学习一下PHP中协程的相关概念的使用方法。

分成上下两篇文章吧,这篇主要讲一下基础概念。

协程是什么?

在以前的Lua学习笔记三中可以看到,协程与多线程的比较,有自己的堆栈、局部变量、指令指针等,但是协程本身与其他协程共享全局变量。主要不同在于,多处理器下,多线程可以真实的同时运行多个线程。而协程任意时刻只能有一个在真实运行,并且只有在明确要求被挂起时才会挂起。

PHP中协程如何理解?

这里引用知乎赵老师的答案,说的比较好理解。具体来说,一个包含yeild的php函数,就是协程,他有阶段性的结算值 yield $var, 但是代码并不返回,php的调度者接到这个值后,喂给一个generatorgenerator是个实现了iterator接口的+和协程通讯接口(比如send方法)的实例,所以可以用在for循环里(另个接口负责和协程通讯)。那么generator收到了这个协程的阶段性的值后,他喂给for循环,等for循环下一次循环的时候,他又启动这个协程,协程从上次中断的点继续执行,继续计算,继续yeild值给generatorgenerator喂for循环,继续循环,直到协程执行完毕。

相关函数

final class Generator implements Iterator {
    public function rewind();     // 返回到迭代器的第一个元素。
    public function valid();      // 返回false如果迭代器已经关闭,否则返回true
    public function current();    // 返回当前yield值.
    public function key();        // 返回当前yield键名.
    public function next();       // 恢复生成器的执行。
    public function PS_UNRESERVE_PREFIX_throw(Exception $exception) {};//抛出异常
    public function send($value); // 将传入的值作为yield表达式的结果并且恢复发生器的执行。
}

简单例子

简单的迭代器给foreach使用

function my_range($start, $end, $step = 1) {
    for ($i = $start; $i <= $end; $i += $step) {
        yield $i;
    }
}
foreach (my_range(1, 5) as $num) {
    echo $num;
}
//output 12345

带send可以交互的例子

function gen() {
    $ret = (yield 'a');
    echo $ret;
    $ret = (yield 'b');
    echo $ret;
}

$gen = gen();
$ret = $gen->current();
echo $ret;
$ret = $gen->send("c");
echo $ret;
$ret = $gen->send("d");
echo $ret;
//output acbd

带抛出异常的例子

function gen() {
    try{
        $ret = (yield 'a');
        echo $ret;
        $ret = (yield 'b');
        echo $ret;
    } catch (Exception $ex) {
        echo $ex->getMessage();
    }
}

$gen = gen();
$ret = $gen->current();
echo $ret;
$ret = $gen->send("c");
echo $ret;
$ret = $gen->throw(new Exception("d"));
var_dump($ret);

//output acbdNULL

那么能用来干什么呢?

我们来看看,协程可以自己主动出让执行权,把不需要抢占的操作时间(比如socket等待链接)让出来,并且可以和调用方通过yield的方式传递信息。显而易见,他可以用来做多任务调度

PHP中协程实现多任务调度,鸟哥有一篇翻译的文章里有讲解,网上能找到的大部分资料,都跟这篇相关。但是至少在我看来,理解起来还是蛮复杂的。这里针对那篇文章的前半部分做一个笔记,忽略后面关于独立堆栈协程的部分。

  1. function里使用yield关键字,将生成迭代器。这样调用functionName()时,其实得到的是一个迭代器对象,而并没有实际运行程序。
  2. 为什么要走系统调用SystemCall这一层呢?模拟进程和系统会话的方式,控制权限。通过给yield表达式传递信息来与调度器通信,yield既是中断也是传递给调度器的方式。
  3. SystemCall 包含一个回调函数,他自己本身可以被执行。被执行时实际上是调用了这个回调函数,入参是某个task和调度器。
  4. SystemCall其实并没有其他作用,只是在协程函数里面跟在yield后面传给调度器来执行。
  5. 注意SplQueue塞进去的对象其实是引用(PHP里对象入参都是引用,不只是SplQueue)!外面对象改了,里面也会变。

为什么忽略协程堆栈?

我打算在第二篇文章中,把有赞的zan framework里关于协程的部分抽出来,针对性的说一下包含子协程额多任务调度。当然主要想偷个懒。

不过个人感觉zan框架里的协程部分,比之前说的那篇文章要好理解一些。

回到顶部