参考文章:https://www.cnblogs.com/tingyugetc/p/6347286.html
PHP生成器是5.5.0引入的功能,生成器实际上就是简单的迭代器。生成器会根据需求计算产出迭代的值,而标准的PHP迭代器经常在内存中执行迭代操作,这要预先计算出数据集,性能较低。如果使用特定的防护计算大量数据,可以使用生成器,即时计算并产出后续值,不占用内存。
yield和生成器
相比较迭代器,生成器提供了一种更容易的方法来实现简单的对象迭代,性能开销和复杂性都大大降低。
一个生成器函数看起来像一个普通的函数,不同的是普通函数返回一个值,而一个生成可以yield生成许多它所需要的值,并且每一次的生成返回值只是暂停当前的执行状态,当下次调用生成器函数时,PHP会从上次暂停的状态继续执行下去。
function xrange($start, $end, $step = 1) {
for ($i = $start; $i <= $end; $i += $step) {
yield $i;
}
}
foreach (xrange(1, 1000000) as $num) {
echo $num, "<br>";
}
我们在使用生成器的时候可以像关联数组那样指定一个键名对应生成的值。如下生成一个键值对与定义一个关联数组相似。
function xrange($start, $limit, $step = 1) {
for ($i = $start, $j = 0; $i <= $limit; $i += $step, $j++) {
// 给予键值
yield $j => $i;
}
}
$xrange = xrange(1, 10, 2);
foreach ($xrange as $key => $value) {
echo $key . ' => ' . $value . "\n";
}
实际上生成器函数返回的是一个Generator对象,这个对象不能通过new实例化,并且实现了Iterator接口。
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的接口之外Generator还添加了send方法,用来向生成器传入一个值,并且当做yield表达式的结果,然后继续执行生成器,直到遇到下一个yield后会再次停住。
function printer() {
while(true) {
echo 'receive: ' . yield . "\n";
}
}
$printer = printer();
$printer->send('Hello');
$printer->send('world');
以上的例子会输出:
receive: Hello
receive: world
在上面的例子中,经过第一个send()方法,yield表达式的值变为Hello,之后执行echo语句,输出第一条结果receive: Hello,输出完毕后继续执行到第二个yield处,只不过当前的语句没有执行到底,不会执行输出。如果将例子改改就能够看出来yield的继续执行到哪里。
function printer() {
$i = 1;
while(true) {
echo 'this is the yield ' . $i ;
echo 'receive: ' . yield ;
$i++;
}
}
$printer = printer();
$printer->send('Hello');
$printer->send('world');
这次的输出便会变为:
this is the yield 1
receive: hello
this is the yield 2
receive: world
this is the yield 3
上面的流程我简单说下:第一步执行$printer->send('Hello');
进入while循环,先输出this is the yield 1
然后输出 receive: hello
之后
i++,
i变为2,然后由于while(true)又再次循环,还是先输出this is the yield 2
(因为现在$i已经是2了),
然后运行到 echo 'receive: ' . yield
; 由于现在代码没有给他send()了,所以就执行到这停止了;然后就执行第二步$printer->send('world');
这里是关键,第二次send后,他会从上次暂停的状态继续执行下去。所以就执行代码echo 'receive: ' . yield;
对应结果是receive: world
,然后
i++,
i变为3,循环没有结束,执行echo 'this is the yield ' . $i;
对应结果是 this is the yield 3
这边可以清楚的看出send之后的继续执行到第二个yield处,之前的代码照常执行。
我们再对例子进行适当的修改:
function printer() {
$i = 1;
while(true) {
echo 'this is the yield ' . (yield $i) . "\n";
$i++;
}
}
$printer = printer();
var_dump($printer->send('first'));
var_dump($printer->send('second'));
执行一下会发现结果为:
this is the yield first
int(2)
this is the yield second
int(3)
让我们来看一下,是不是发现了问题,跑出来的结果不是从1开始的而是从2开始,这是为啥嘞,我们来分析一下:
在此之前我们先来跑另外一段代码:
function printer() {
$i = 1;
while(true) {
echo 'this is the yield ' . (yield $i) . "\n";
$i++;
}
}
$printer = printer();
var_dump($printer->current());
var_dump($printer->send('first'));
var_dump($printer->send('second'));
这个时候我们会发现执行的结果变成了:
int(1)
this is the yield first
int(2)
this is the yield second
int(3)
可以看到在第一次调用生成器函数的时候,生成器已经执行到了第一个yield表达式处,所以在$printer->send('first')
之前,生成器便已经yield 1出来了,只是没有对这个生成的值进行接收处理,
在send()了之后,echo语句便会紧接着完整的执行,执行完毕继续执行$i++,下次循环便是var_dump(2)。
至此,我们看到了yield不仅能够返回数据而且还可以接收数据,而且两者可以同时进行,此时yield便成了数据双向传输的工具,这就为了实现协程提供了可能性。
补充:
<?php
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 send($value); // 将传入的值作为yield表达式的结果并且恢复发生器的执行。
}
?>
function xrange ()
{
while (1) {
$a = (yield '11'); //yield 表达式
echo $a.'<br>';
}
}
$a = xrange();//返回一个生成器
echo $a->current();//返回当前产生的值 结果为11
//打印的结果11,不是因为echo $a.'<br>';这句导致的,而是(yield '11')这句,之前一直以为是echo $a的结果
//如果这个时候在echo $a->current();后面加一句$a->send('33');
function xrange ()
{
while (1) {
echo 'ppp'.'<br>';
$a = (yield '11'); //yield 表达式
echo $a.'<br>';
}
}
$a = xrange();//返回一个生成器
echo $a->current().'<br>';//返回当前产生的值
$a->send('33');//向生成器中传入一个值,并且当做 yield 表达式的结果,然后继续执行生成器。
其结果:
ppp
11
33
ppp
//当运行echo $a->current().'<br>';的时候结果是:
ppp
11
这个时候暂停在$a = (yield '11'); 这里,随后运行$a->send('33'),在暂停点继续运行,结果是:
33
ppp
至于接下来的协程的知识,水平有限不好介绍,还是看鸟哥的原文比较直接,里面例子很丰富,介绍的很详尽。