基础特性
一个函数中有yield出现的话,这个函数就是生成器
function &gen_reference() {
$value = 3;
while ($value > 0) {
yield $value;
}
// 如果省略则函数返回null
return true;
}
var_dump(gen_reference()); // object(Generator)#1 (0) {}
既然是生成器,执行顺序就和生成器的顺序是一样的,如下下所示:
// 使用引用生成值
function gen_reference() {
$value = 3;
while ($value > 0) {
yield $value;
$value -= 1;
echo 2;
}
return 1;
}
$gen = gen_reference();// 初始化
// 执行gen_reference函数,直到阻塞在yield,然后这里继续往下执行,输出1
// foreach刚开始相当于调用这个方法
// 一开始调用rewind,valid,current,key,next方法都会开始执行函数里的内容
$gen->rewind();
echo 1;
$gen->valid();
// 获取yield的值,gen_reference方法还是阻塞在yield
$val1 = $gen->current();
// 多次调用current还是得到当前值,也就是3,,gen_reference方法还是阻塞在yield
$val2 = $gen->current();
// 获取yield的key,gen_reference方法还是阻塞在yield
$key1 = $gen->key();
// 多次调用key还是得到当前值,也就是0,gen_reference方法还是阻塞在yield
$key2 = $gen->key();
$gen->next();
也可以用foreach自动执行上述流程,方法前面要加&
function &gen_reference() {
$value = 3;
while ($value > 0) {
yield $value;
}
}
// 引用的方式,会自动修改函数了的value值
foreach (gen_reference() as &$number) {
echo (--$number).'... ';
}
应用
读取大文件
可以使用迭代器读取超过10个G的大文件
function readTxt()
{
$handle = fopen("./test.txt", 'rb');
while (feof($handle) === false) {
yield fgets($handle);
}
fclose($handle);
}
foreach (readTxt() as $key => $value) {
sleep(1);
echo $value;
}
协程
和go的协程不一样的是,这个协程需要自己调度,并且不能并行执行,可以通过共享变量的方式进行通信
class Coroutine
{
/**
* @param callable $callback
* @return Generator
*/
public static function create(callable $callback): Generator
{
// 调用含有yield的函数,是返回Generator实例
return (function () use ($callback) {
// 把各自的逻辑封装在闭包里,等待调用执行
yield $callback;
})();
}
/**
* 分配各个生成器的执行
* @param array $cos
* @throws Exception
*/
public static function run(array $cos)
{
$cnt = count($cos);
while ($cnt > 0) {
$loc = random_int(0, $cnt - 1); // 用 random 模拟调度策略。
// 取出指定的闭包,并且执行,其实这个跟自己在数组里定义闭包,然后随机执行某个数组元素的闭包是一样的
// 所以这种实现的方式,优势在哪里呢?
// 应该是在于,如果yield后还有值的话,可以实现逻辑可以执行到一半的时候,将控制器返给主程序
$cos[$loc]->current()();
array_splice($cos, $loc, 1);
$cnt--;
}
}
}
$co = new Coroutine();
$cos = [];
for ($i = 1; $i <= 10; $i++) {
// 初始化生成器
$cos[] = $co::create(function () use ($i) {
echo "Co.{$i}.", PHP_EOL;
});
}
$co::run($cos);
生成大数组
function xrange($start,$limit) {
for($i = $start;$i<=$limit;$i++){
yield $i;
}
}
// 使用生成器,可以用于迭代
foreach (xrange(0,10) as $number) {
echo $number;
}
// 这个会报错,不支持这样的方式
echo count(xrange(0,1000));