基础特性

一个函数中有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));
最后修改:2023 年 12 月 30 日
如果觉得我的文章对你有用,请随意赞赏