php在php5.5的时候引入了generator和coroutine,从核心上提出了一种方法去写不阻塞的IO,当然这和node的event loop还是有比较大的区别的,它的主要理念是: 把几个大任务分别分成多个小步轮流执行,有某个小任务在等待系统io的话,就跳过它,执行下一个小任务,这样总体提升了代码的效率 。
0x1:yield表达式是什么?
非常简单,描述yield表达式的只有两个关键词: 中断点 和 占位符 (自己总结的两点,只属于一种感性的记忆方式,并不是官方给出的专业词汇)。
举个简单的例子:
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
.
Task
对象以Generator对象为参数初始化,一个Task
分成了多个小步执行。Scheduler
对象负责调度任务,什么叫做调度呢?就是分别轮流执行多个Task
对象的每一步,如果某一步还在等待IO就跳过去这一步。-
SystemCall
是Task
的一个小步,假设Task A 对象的多个小步为 ‘——+——‘, 执行到+
这一步就执行SystemCall的任务。还有一些额外的对象去给任务调度添加功能:
CoroutineReturnValue
把数值类型封装成类,用在处理coroutine之间的嵌套。CoSocket
封装了socket的系列操作。-
Log
输出日志到cli。具体怎么实现,还是源码来的实在,仓库地址 https://github.com/Jamlee/coroutine
参考文档:
http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html