rewind(), current(), next(), valid()
比如下面一个简单的例子<?php
function go(){
yield;
echo 'yield ';
yield;
echo 'end';
};
$test = go();
假如函数中没有那两个 yield 关键字,上述代码会输出 yield end,可是加上 yield,会发现程序什么输出都没有,这是因为 yield 相当于一个断点,函数走到这里会停止,让出 cpu ,这也是协程的工作原理
<?php
$yield= function(){
yield;
echo 'yield ';
yield;
echo 'end';
};
$test = $yield();// 生成一个Geneator 的实例对象,对象实例化的时候,会执行一次 rewind() 相当于生成器函数执行到第一次 yield 处中断函数
$test->next();// 输出 yield 调用 next() 就是使程序继续执行,执行到下一个 yield 处再次中断,所以会输入 yield
$test->next();// 输出 end 同上,这个执行后,函数内部不再有中断点
var_dump($test->valid()); // 这里会输出 false 说明迭代结束
<?php
// 上面的例子中,只单独使用了 yield 关键字,其实 yield 左右两边都是可以跟表达式和变量的
// 先来一个简单的便于理解
function work(){
$str = yield 'first yield';
echo $str;
yield 'end';
}
$s = work();
var_dump($a->current()); // string(11) "first yield"
var_dump($s->next()); // NULL
// 如果将上面的 var_dump($s->next()) 换成 $s->send('php') 会输出 php 并得到 string(3) "end"
迭代器的 current()
以及 send()
会返回当前 yield 右侧的值,如果右侧没有,则返回空,next()
则是继续上一个断点,继续执行到下一个 yield 或者迭代器结束的地方,比如 return 语句,生成器的迭代是通过 valid() 来判断的,valid() 返回 false 时说明迭代结束
<?php
$work = function(){
$i = 0;
while(++$i < 10){
$str = yield $i;
var_dump($str);
}
};
// case 1
$test = $work();
foreach($test as $v){
echo $v,PHP_EOL;
}
// 上面的foreach 实际上就是
foreach($test->rewind(),$v = $test->current();$test->valid();$test->next(),$v = $test->current()){
echo $v,PHP_EOL;
}
// case 2
$i = 0;
$test = $work();
while($test->valid()){
$test->send('send '.$i++);
}
// 上述两段代码输出不同 能看出 next() 就是仅仅向下执行,不传入数据
// send() 会传入数据,在生成器中被 yield 接收,用来传递给左侧变量(没有赋值操作的话的话 send() 也就没有使用的意义)
一个更能看出的例子
<?php
$s = (function($num){
while(--$num){
var_dump(yield);
}
})(10);
$i = 0;
while($s->valid()){
//$s->next();
$s->send(++$i);// 试着将这条语句注释,执行上方的语句,看看有什么不同的效果
}
除了可以设置生成器每次迭代的值之外,也可以设置键
<?php
function work($i){
while(--$i > 0){
yield $i * $i => $i;
}
}
$test = work(5);
while($test->valid()){
echo $test->key(),'----',$test->current(),PHP_EOL;
$test->next();
}
如果不使用 $key => $value
的格式,右侧的变量只能作为 value,键值默认为数字索引
使用协程写两个例子
<?php
const NUM = 10; // 设置生产个数,作为终止条件
$consumer = (function(){
while(true){
$receive = yield; // 设置断点
echo 'receive '.$receive,PHP_EOL;
echo 'consume end',PHP_EOL;
}
})();
$producer = (function($num) use ($consumer){
echo 'start produce',PHP_EOL,'-----',PHP_EOL;
while(--$num > 0){
echo 'produce num '.$num,PHP_EOL;
$consumer->send('produce num '.$num); // 生产后通知 consumer 消费
}
echo '------',PHP_EOL,'produce end';
})(NUM);
再写一个之前线程实现的 线程同步打印 ABC
<?php
/**
* 实现方式,A 打印后通知 B ,B 打印后通知 C,C 打印后返回 A 处,继续执行
* 所以将 B 和 C 用生成器实现,A 调用 B,B 调用 C
*/
const NUM = 10; // 打印次数
const SLEEP_TIME = 1e6; //休眠时间
$c = (function(){
while(true){
yield;
echo 'C';
usleep(SLEEP_TIME);// 为了看出效果,延时一下
}
})();
$b = (function() use ($c){
while(true){
yield;
echo 'B';
usleep(SLEEP_TIME);
$c->next();
}
})();
$a = (function($num) use ($b){
while (--$num > 0) {
echo 'A';
usleep(SLEEP_TIME);
$b->next();
}
echo PHP_EOL,'print end';
})(NUM);
通过对协程的使用,我还是认为协程只是调整了代码的执行顺序,(能通过协程实现的,我们可以自己改写代码顺序来实现),并不能实现并行。协程切换的代价小,但是依旧不能取代线程的作用,协程如果能配合线程,来实现异步,将任务分发给线程,通过协程调度任务,应该会是一个不错的方案
原文:https://www.cnblogs.com/mlover/p/11134493.html