yield探索

内存观察

迭代输出一个从1开始一直到10000的数组,步进为1。

随手一写

<?php
$start_mem = memory_get_usage();
$arr = range( 1, 10000 );
foreach( $arr as $item ){
    echo $item."\n";
}
$end_mem = memory_get_usage();
fprintf(STDOUT, " use mem : %d bytes \n", $end_mem - $start_mem);

// use mem : 266328 bytes

使用yield

<?php
$start_mem = memory_get_usage();
function yield_range($start, $end): Generator
{
    while ($start <= $end) {
        $start++;
        yield $start;
    }
}

foreach (yield_range(0, 9999) as $item) {
    echo $item . "\n";
}
$end_mem = memory_get_usage();
fprintf(STDOUT, " use mem : %d bytes \n", $end_mem - $start_mem);

// use mem : 32 bytes

探索

首先,我们观察一下yield_range这个函数跟普通函数不一样的地方,就是普通函数往往都是使用return来返回结果,而这个中则是yield。其次是普通函数中return只能返回一次,这个yield能返回好多次。

那么,我们来分析一波儿这个神奇的yield_range函数。这个yield关键字到底返回的是什么?我们简单看一下:

<?php

function yield_range( $start, $end ){
    while( $start <= $end ){
        $start++;
        yield $start;
    }
}
$rs = yield_range( 1, 100 );
var_dump( $rs );

object(Generator)#1 (0) {
}

yield返回的是一个叫做Generator(中文名就是生成器)的object对象,而这个生成器是实现了Iterator接口。
Generator结构

<?php

/**
* @template TKey of array-key
* @template TSend
* @template TReturn
* @template TYield
*
* Generator objects are returned from generators, cannot be instantiated via new.
* @link https://secure.php.net/manual/en/class.generator.php
* @link https://wiki.php.net/rfc/generators
*
* @template-implements Iterator<TKey, TYield>
*/
final class Generator implements Iterator
{
    /**
     * Throws an exception if the generator is currently after the first yield.
     * @return void
     */
    public function rewind(): void {}

    /**
     * Returns false if the generator has been closed, true otherwise.
     * @return bool
     */
    public function valid(): bool {}

    /**
     * Returns whatever was passed to yield or null if nothing was passed or the generator is already closed.
     * @return TYield
     */
    public function current(): mixed {}

    /**
     * Returns the yielded key or, if none was specified, an auto-incrementing key or null if the generator is already closed.
     * @return TKey
     */
    #[LanguageLevelTypeAware(['8.0' => 'mixed'], default: 'string|float|int|bool|null')]
    public function key() {}

    /**
     * Resumes the generator (unless the generator is already closed).
     * @return void
     */
    public function next(): void {}

    /**
     * Sets the return value of the yield expression and resumes the generator (unless the generator is already closed).
     * @param TSend $value
     * @return TYield|null
     */
    public function send(mixed $value): mixed {}

    /**
     * Throws an exception at the current suspension point in the generator.
     * @param Throwable $exception
     * @return TYield
     */
    public function PS_UNRESERVE_PREFIX_throw(Throwable $exception): mixed {}

    /**
     * Returns whatever was passed to return or null if nothing.
     * Throws an exception if the generator is still valid.
     * @link https://wiki.php.net/rfc/generator-return-expressions
     * @return TReturn
     * @since 7.0
     */
    public function getReturn(): mixed {}

    /**
     * Serialize callback
     * Throws an exception as generators can't be serialized.
     * @link https://php.net/manual/en/generator.wakeup.php
     * @return void
     */
    public function __wakeup() {}
}

所以,既然实现了Iterator接口,所以可以有如下代码:

<?php

function yield_range( $start, $end ){
    while( $start <= $end ){
        yield $start;
        $start++;
    }
}
$generator = yield_range( 1, 10 );
// valid() current() next() 都是Iterator接口中的方法
while( $generator->valid() ){
    echo $generator->current().PHP_EOL;
    $generator->next();
}

重点

这个yield_range函数似乎能够记住它上一次运行到哪儿了,上一次运行的结果是什么,然后紧接着在下一次运行的时候继续从上次终止的地方继续开始。这不是普通的PHP函数可以做得到的!
紧接着,我们需要认识一个生成器对象的一个方法,叫做send,简单看下下面这坨代码:

<?php

function yield_range($start, $end)
{
    while ($start <= $end) {
        $ret = yield $start;
        $start++;
        echo "yield receive : " . $ret . PHP_EOL;
    }
}

$generator = yield_range(1, 10);
$generator->send(100);

send方法可以修改yield的返回值。如果把send放到foreach中呢。

<?php

function yield_range( $start, $end ){
    while( $start <= $end ){
        $ret = yield $start;
        $start++;
        echo "yield receive : ".$ret.PHP_EOL;
    }
}
$generator = yield_range( 1, 10 );
foreach( $generator as $item ){
    $generator->send( $generator->current() * 10 );
}

yield receive : 10
yield receive :
yield receive : 30
yield receive :
yield receive : 50
yield receive :
yield receive : 70
yield receive :
yield receive : 90
yield receive :

yield的最重要作用就是:自己中断一坨代码的执行,然后主动让出CPU控制权给路人甲;然后又能通过一些方式从刚才中断的地方恢复运行。

<?php

function gen1() {
    for( $i = 1; $i <= 10; $i++ ) {
        echo "GEN1 : {$i}".PHP_EOL;
        sleep( 1 );
        yield;
    }
}
function gen2() {
    for( $i = 1; $i <= 10; $i++ ) {
        echo "GEN2 : {$i}".PHP_EOL;
        sleep( 1 );
        yield;
    }
}
$task1 = gen1();
$task2 = gen2();
while( true ) {
    echo $task1->current();
    echo $task2->current();
    $task1->next();
    $task2->next();
}

明白了这段代码就明白了 yield 的原理。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇