php在php5.5的时候引入了generator和coroutine,当然这和node的event loop还是有比较大的区别的。 yield 表达式 和php封装过系统底层的函数(select函数)可以一起写出非阻塞的io。

0x1: yield表达式是什么?

非常简单,描述yield表达式的只有两个关键词: 中断点 和 占位符(自己总结的两点,只属于一种感性的记忆方式,并不是官方给出的专业词汇)。
举个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

function gen() {
$tid = (yield 1 + 1);
for ($i = 1; $i <= 10; ++$i) {
echo "This is $tid task iteration $i.\n";
yield $i;
}
}

//$t1是[generator](http://php.net/manual/en/class.generator.php)类的实例(instance)
$t1 = gen();
//取出yield后面的表达式的结构,并没有进行赋值就暂停了当前的操作 `$tid = (yield 1 + 1) `,(特性一:中断点)
$r1 = $t1->current();
//结果为 2
echo $r1;
//将字符'+++'(特性二:占位符),替换到刚才暂停的地方 `$tid = '+++++'`,并进入for循环,遇见yield表达式,获取yield 表达式后面的值,并保存当前的局部变量的值,yield后面是$i,返回$i
var_dump($t1->send('+++'));

这里 current方法是暂停并返回获取当前yield表达式的值。send方法是先替换之前暂停时的yield表达式所处的位置的值,再开始执行,直到遇到下一个yield表达式,再取表达式的结果,暂停并保存当前的局部变量的值。在这里我注意到send方法总是同时得确定两个yield表达式的位置,第一个yield表达式的值被替换,再去寻找第二个表达式的值(yield $i里面的$i),再次保存当前的状态,返回 yield表达式 后面的值。依次类推。
这里有个需要思考的问题就是如果一开始就用send方法不用current()会怎么样? 答案是send方法在第一次运行之前会隐含调用rewind方法,会在函数第一个yield的地方中断保存局部变量,但是忽略它的返回值。

0x2: coroutine是什么?

Coroutines are computer program components that generalize subroutines for nonpreemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations.

简单的说:coroutine(协同程序) 提供一种方法中断当前执行,保存当前的局部变量,下次再过来又可以恢复当前局部变量继续执行。在php里就是一个大的任务分别分成小的任务,轮流执行。而中断和恢复就是靠的yield表达式来实现。

0x3: 使用yield表达式 实现非阻塞IO的例子

在这里主要有三个参与对象共同去实现任务调度:Task, Scheduler, SystemCall.

  1. Task 对象以Generator对象为参数初始化,一个Task 分成了多个小步执行。
  2. Scheduler 对象负责调度任务,什么叫做调度呢?就是分别轮流执行多个Task对象的每一步,如果某一步还在等待IO就跳过去这一步。
  3. SystemCall是Task的一个小步,假设Task A 对象的多个小步为 ‘— — + — —‘, 执行到+这一步就执行SystemCall的任务。

还有一些额外的对象去给任务调度添加功能:

  1. CoroutineReturnValue 把数值类型封装成类,用在处理coroutine之间的嵌套。
  2. CoSocket 封装了socket的系列操作。
  3. Log 输出日志到cli。

具体怎么实现,还是源码来的实在,仓库地址 https://github.com/Jamlee/coroutine

参考文档:

http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html