🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
生成器优点: * 生成器会对PHP应用的性能有非常大的影响 * PHP代码运行时节省大量的内存 * 比较适合计算大量的数据 ## **[yield](https://www.php.net/manual/zh/language.generators.syntax.php#control-structures.yield)** 生成器函数的核心是**yield**关键字[yield](https://www.php.net/manual/zh/language.generators.syntax.php#control-structures.yield)的功能是让程序自己交出控制权,并停留在当前的执行位置,yield看起来像一个return,不同之处在于普通return会返回值并终止函数的执行,而**yield会返回给一个值给循环调用此生成器的代码**并且只是**暂停执行生成器**函数,同时也可以接收生成器传递过来的数据并替换当前yield表达式,可以看成是双向管道 * (迭代)生成器也是一个函数,不同的是这个函数的返回值是依次返回(遍历返回),而不是只返回一个单独的值 * PHP5.5一个比较好的新功能是加入了对迭代生成器和协程的支持. * 生成器是一种可中断的函数, 在它里面的yield构成了中断点 >[info]生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组,那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样,和普通函数只返回一次不同的是,生成器可以根据需要 yield 多次,以便生成需要迭代的值。 > >* 一个简单的例子就是使用生成器来重新实现 range () 函数。 标准的 range () 函数需要在内存中生成一个数组包含每一个在它范围内的值,然后返回该数组,结果就是会产生多个很大的数组。 比如,调用 range (0, 1000000) 将导致内存占用超过 100 MB。 做为一种替代方法,我们可以实现一个 xrange () 生成器,只需要足够的内存来创建 Iterator 对象并在内部跟踪生成器的当前状态,这样只需要不到 1K 字节的内存。 >* 当一个生成器被调用的时候,它返回一个可以被遍历的对象。当你遍历这个对象的时候 (例如通过一个 foreach 循环),PHP 将会在每次需要值的时候调用生成器函数,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。 一旦不再需要产生更多的值,生成器函数可以简单退出,而调用生成器的代码还可以继续执行,就像一个数组已经被遍历完了。 ``` <?php function xrange($start, $end, $step = 1) { for ($i = $start; $i <= $end; $i += $step) { yield $i; //自动生成数字索引键 //指定键名 //$id=$i-1; //yield $id => $i; //生成null值 //yield;//自动生成数字索引键 } } //调用xrange(1,1000000)的时候, xrange()函数里代码其实并没有真正地运行. 它只是返回了一个迭代器 $range = xrange(1, 1000000); var_dump($range); // object(Generator)#1 var_dump($range instanceof Iterator); // bool(true) //这也解释了为什么xrange叫做迭代生成器, 因为它返回一个迭代器, 而这个迭代器实现了Iterator接口. /* 调用迭代器的方法一次, 其中的代码运行一次.例如, 如果你调用$range->rewind(), 那么xrange()里的代码就会运行到控制流第一次出现yield的地方. 而函数内传递给yield语句的返回值可以通过$range->current()获取. 为了继续执行生成器中yield后的代码, 你就需要调用$range->next()方法. 这将再次启动生成器, 直到下一次yield语句出现. 因此,连续调用next()和current()方法, 你就能从生成器里获得所有的值, 直到再没有yield语句出现. 对xrange()来说, 这种情形出现在$i超过$end时. 在这中情况下, 控制流将到达函数的终点,因此将不执行任何代码.一旦这种情况发生,vaild()方法将返回假, 这时迭代结束. */ ?> ``` #### 使用引用来生成值 ``` <?php function &gen_reference() { $value = 3; while ($value > 0) { yield $value; } } /* * 我们可以在循环中修改$number的值,而生成器是使用的引用值来生成,所以gen_reference()内部的$value值也会跟着变化。 */ foreach (gen_reference() as &$number) { echo (--$number).'... '; } ?> ``` ~~~ 输出: 2... 1... 0... ~~~ ### 通过yield from实现生成器委派 在PHP 7中,生成器委派允许您通过使用yield from keyword来从另一个生成器,Traversable对象或数组中生成值。然后外部生成器将从内部生成器,对象或数组中生成所有值,直到它不再有效,之后将在外部生成器中继续执行。如果生成器与yield from一起使用,则expression的yield也将返回内部生成器返回的任何值。 ``` <?php function count_to_ten() { yield 1; yield 2; yield from [3, 4]; yield from new ArrayIterator([5, 6]); yield from seven_eight(); yield 9; yield 10; } function seven_eight() { yield 7; yield from eight(); } function eight() { yield 8; } foreach (count_to_ten() as $num) { echo "$num "; } ?> ``` ``` 以上例程会输出: 1 2 3 4 5 6 7 8 9 10 ``` # **协程:** ## [在PHP中使用协程实现多任务调度](http://www.laruence.com/2015/05/28/3038.html) >正如你能看到,这儿yield没有作为一个语句来使用, 而是用作一个表达式, 即它能被演化成一个值. 这个值就是调用者传递给send()方法的值. 如下例子里, yield表达式将首先被”Foo”替代写入Log, 然后被”Bar”替代写入Log. ``` <?php function logger($fileName) { $fileHandle = fopen($fileName, 'a'); while (true) { fwrite($fileHandle, yield . "\n"); } } $logger = logger(__DIR__ . '/log'); $logger->send('Foo'); $logger->send('Bar'); //生成的log文件内容许下: /* Foo Bar */ ``` >[info]上面的例子里演示了yield作为接受者, 接下来我们看如何同时进行接收和发送的例子: ~~~ <?php function gen() { $ret = (yield 'yield1'); var_dump($ret);echo "|y1|<br>"; $ret = (yield 'yield2'); var_dump($ret);//ret2 } $gen = gen(); var_dump($gen->current()); //yield1 初始指针停留在 $ret = (yield 'yield1');位置 echo 111;var_dump($gen->send('ret1'));echo 2222; //yield2 (第一次var_dump send改变了指针停留位置的值且隐式的调用了next指针移动到$ret = (yield 'yield2');) var_dump($gen->current()); //yield2 var_dump($gen->send('ret2')); //null ?> ~~~ ## **多任务协作** 多任务协作这个术语中的“协作”说明了如何进行这种切换的:它要求当前正在运行的任务自动把控制传回给调度器,这样它就可以运行其他任务了。这与“抢占”多任务相反,抢占多任务是这样的:调度器可以中断运行了一段时间的任务,不管它喜欢还是不喜欢。协作多任务在Windows的早期版本(windows95)和Mac OS中有使用,不过它们后来都切换到使用抢先多任务了。理由相当明确:如果你依靠程序自动传回 控制的话,那么坏行为的软件将很容易为自身占用整个CPU,不与其他任务共享。  这个时候你应当明白协程和任务调度之间的联系:yield指令提供了任务中断自身的一种方法,然后把控制传递给调度器。因此协程可以运行多个其他任务。更进一步来说,yield可以用来在任务和调度器之间进行通信。