成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

PHP的生成器

LMou / 2263人閱讀

摘要:它最簡單的調(diào)用形式看起來像一個(gè)申明,不同之處在于普通會(huì)返回值并終止函數(shù)的執(zhí)行,而會(huì)返回一個(gè)值給循環(huán)調(diào)用此生成器的代碼并且只是暫停執(zhí)行生成器函數(shù)。

0x01 寫在前面

本文主要介紹:

Generator的簡單用法。

Generator的底層實(shí)現(xiàn)。

本文比較長,可能會(huì)耗費(fèi)你比較多的時(shí)間。如果你比較了解Generator的用法,僅想了解底層實(shí)現(xiàn),可以直接跳到底層實(shí)現(xiàn)部分。

本文分析的PHP源碼版本為:7.0.29。

目錄

0x01 寫在前面

0x02 Generator的用法

2.1 生成器總覽

2.2 生成器對象

2.3 生成器語法

2.3.1 yield關(guān)鍵字

2.3.2 yield from

2.4 Generator類

2.5 Generator方法

2.5.1 Generator::__wakeup()

2.5.2 Generator::send()

2.5.3 Generator::throw()

0x03 生成器的底層實(shí)現(xiàn)

3.1 Generator類的注冊及其存儲(chǔ)結(jié)構(gòu)

3.2 zend_generator結(jié)構(gòu)體

3.3 生成器對象的創(chuàng)建

3.3.1 編譯階段

3.3.2 執(zhí)行階段

3.4 yield生成值

3.5 生成器對象的訪問

3.5.1 使用生成器對象接口訪問

3.5.2 使用foreach訪問

3.6 生成器的終止

3.7 小結(jié)

此文為個(gè)人的學(xué)習(xí)筆記,意在對自己的學(xué)習(xí)過程進(jìn)行總結(jié)。由于個(gè)人能力有限,錯(cuò)漏在所難免,歡迎批評指正。
0x02 Generator的用法

Generator,中文翻譯:生成器,是PHP 5.5開始支持的語法。

2.1 生成器總覽
生成器提供了一種更容易的方法來實(shí)現(xiàn)簡單的對象迭代,相比較定義類實(shí)現(xiàn) Iterator 接口的方式,性能開銷和復(fù)雜性大大降低。

生成器允許你在 foreach 代碼塊中寫代碼來迭代一組數(shù)據(jù)而不需要在內(nèi)存中創(chuàng)建一個(gè)數(shù)組, 那會(huì)使你的內(nèi)存達(dá)到上限,或者會(huì)占據(jù)可觀的處理時(shí)間。相反,你可以寫一個(gè)生成器函數(shù),就像一個(gè)普通的自定義函數(shù)一樣, 和普通函數(shù)只返回一次不同的是, 生成器可以根據(jù)需要 yield 多次,以便生成需要迭代的值。

2.2 生成器對象
當(dāng)一個(gè)生成器的函數(shù)的被調(diào)用時(shí),對返回內(nèi)置類Generator的一個(gè)實(shí)例化對象。這個(gè)對象實(shí)現(xiàn)了Iterator接口,跟迭代器一樣可以向前迭代,并且提供了維護(hù)這個(gè)對象的狀態(tài)的接口,包括向它發(fā)送值和從它接收值。
2.3 生成器語法
一個(gè)生成器函數(shù)看起來像一個(gè)普通的函數(shù),不同的是普通函數(shù)返回一個(gè)值,而一個(gè)生成器可以yield生成許多它所需要的值。

當(dāng)一個(gè)生成器被調(diào)用的時(shí)候,它返回一個(gè)可以被遍歷的對象.當(dāng)你遍歷這個(gè)對象的時(shí)候(例如通過一個(gè)foreach循環(huán)),PHP 將會(huì)在每次需要值的時(shí)候調(diào)用生成器函數(shù),并在產(chǎn)生一個(gè)值之后保存生成器的狀態(tài),這樣它就可以在需要產(chǎn)生下一個(gè)值的時(shí)候恢復(fù)調(diào)用狀態(tài)。

一旦不再需要產(chǎn)生更多的值,生成器函數(shù)可以簡單退出,而調(diào)用生成器的代碼還可以繼續(xù)執(zhí)行,就像一個(gè)數(shù)組已經(jīng)被遍歷完了

PHP 5是不可以有返回值的,如果這樣做會(huì)導(dǎo)致編譯錯(cuò)誤。但是一個(gè)空的return語句是可以的,這會(huì)終止生成器的執(zhí)行。PHP 7支持返回值,使用Generator::getReturn()獲取返回值。

2.3.1 yield關(guān)鍵字
生成器函數(shù)的核心是yield關(guān)鍵字。它最簡單的調(diào)用形式看起來像一個(gè)return申明,不同之處在于普通return會(huì)返回值并終止函數(shù)的執(zhí)行,而yield會(huì)返回一個(gè)值給循環(huán)調(diào)用此生成器的代碼并且只是暫停執(zhí)行生成器函數(shù)。

理論顯得空洞無力,show me your code,那就來看一段簡單的代碼,以便更容易理解生成器語法:

代碼片段2.1:


說明:

執(zhí)行$generator = gen_one_to_three();,這時(shí)不會(huì)執(zhí)行生成器函數(shù)gen_one_to_three()里面的代碼,而是返回一個(gè)生成器對象,也就是說$generator是一個(gè)生成器對象。

foreach ($generator as $value)遍歷生成器對象,因?yàn)镚enerator實(shí)現(xiàn)了Iterator接口,可以用foreach進(jìn)行迭代。這時(shí)就會(huì)調(diào)用生成器函數(shù)gen_one_to_three(),于是執(zhí)行g(shù)en_one_to_three()的代碼。

因?yàn)槭鞘状握{(diào)用,所以從開始執(zhí)行,執(zhí)行for循環(huán),此時(shí)$i=1,執(zhí)行到yield $i;相當(dāng)于生成了一個(gè)值1,并且保存了當(dāng)前的狀態(tài)(比如$i=1、執(zhí)行yield $i;這里)并暫停執(zhí)行。

foreach獲取到這個(gè)值1,并echo輸出。

繼續(xù)遍歷foreach,這是會(huì)調(diào)用生成器函數(shù),并恢復(fù)從上次保存的狀態(tài)(包括變量值,和執(zhí)行到的位置)繼續(xù)執(zhí)行,$i++,這是$i=2。

for循環(huán)繼續(xù)執(zhí)行,再次執(zhí)行yield $i;相當(dāng)于生成一個(gè)值2,并且保存了當(dāng)前的狀態(tài)并暫停執(zhí)行。

foreach獲取到這個(gè)值2,并echo輸出。

foreach繼續(xù)執(zhí)行,繼續(xù)調(diào)用生成器函數(shù),這是$i++,$i=3,執(zhí)行yield $i;生成一個(gè)值3給$value并輸出$value。

foreach繼續(xù)執(zhí)行,但是生成器函數(shù)沒有生成值了(valid()返回false),所以結(jié)束foreach遍歷。

2.3.2 yield from
PHP 7允許您使用yield from關(guān)鍵字從另一個(gè)生成器、Traversable對象或數(shù)組中生成值(后面簡稱委托對象),這叫生成器委托。 生成器將從內(nèi)嵌生成器、對象或數(shù)組中生成所有值,直到它不再有效,然后繼續(xù)生成器的執(zhí)行。

代碼片段2.3.2:

 
上例會(huì)輸出:

1 2 3 4 5 6 7 8 9 10 

以上的引用內(nèi)容來自于PHP幫助手冊,例子也基本來自手冊,我只是加了一些說明,以便幫助更好的理解其語法。

2.4 Generator類

前面說Generator類實(shí)現(xiàn)了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 )
}

Generator比起Iterator接口,增加了send()、throw()以及__wakeup()方法。

既然實(shí)現(xiàn)了Iterator接口,那上面的代碼片段2.3.1也可以改成下面的,執(zhí)行結(jié)果一樣的:

代碼片段2.4.1:

valid()) {
    echo "{$generator->current()}
";
    $generator->next();
}

2.5 Generator方法 2.5.1 Generator::__wakeup()

這是一個(gè)魔術(shù)方法,當(dāng)一個(gè)對象被反序列化時(shí)會(huì)調(diào)用,但生成器對象不能被序列化和反序列化,所以__wakeup()方法拋出一個(gè)異常以表示生成器不能被序列化。

2.5.2 Generator::send()

前面生成器對象部分提到:可以從生成器對象接收值和向它發(fā)送值。yield就是從它接收值,那發(fā)送值是什么呢?就是這個(gè)send()方法。

PHP幫助文檔的介紹:

public mixed Generator::send ( mixed $value )
向生成器中傳入一個(gè)值,并且當(dāng)做 yield 表達(dá)式的結(jié)果,然后繼續(xù)執(zhí)行生成器。 

如果當(dāng)這個(gè)方法被調(diào)用時(shí),生成器不在 yield表達(dá)式,那么在傳入值之前,它會(huì)先運(yùn)行到第一個(gè) yield 表達(dá)式.

先來理解第一段話:

向生成器中傳入一個(gè)值,并且當(dāng)做 yield 表達(dá)式的結(jié)果,然后繼續(xù)執(zhí)行生成器。

yield后生成了值,還可以用這個(gè)生成器對象的send()方法發(fā)送一個(gè)值,而這個(gè)值作為表達(dá)式的結(jié)果,然后在生成器函數(shù)里面可以獲取到這個(gè)值,接著繼續(xù)執(zhí)行生成器??聪旅娴拇a:

代碼片段2.5.1:

send("exit");
}

說明:

執(zhí)行$generator = gen_one_to_three();,這時(shí)不會(huì)執(zhí)行生成器函數(shù)gen_one_to_three()里面的代碼,而是返回一個(gè)生成器對象,也就是說$generator是一個(gè)生成器對象。

foreach ($generator as $value)遍歷生成器對象,因?yàn)镚enerator實(shí)現(xiàn)了Iterator接口,可以用foreach進(jìn)行迭代。這時(shí)就會(huì)調(diào)用生成器函數(shù)gen_one_to_three(),于是執(zhí)行g(shù)en_one_to_three()的代碼。

因?yàn)槭鞘状握{(diào)用,所以從開始執(zhí)行,執(zhí)行for循環(huán),此時(shí)$i=1,執(zhí)行到$cmd = (yield $i);相當(dāng)于生成了一個(gè)值1,并且保存了當(dāng)前的狀態(tài)(比如$i=1、執(zhí)行yield $i;這里)并暫停執(zhí)行。

foreach獲取到這個(gè)值1,賦給$value,并echo輸出。

執(zhí)行$generator->send("exit");向生成器函數(shù)里面發(fā)送值"exit"。

生成器函數(shù)拿到這個(gè)值"exit",作為yield $i;表達(dá)式的值,然后賦給$cmd,也就是$cmd = (yield $i);相當(dāng)于$cmd = "exit";,繼續(xù)執(zhí)行生成器函數(shù)。

if ($cmd === "exit")條件成立,所以執(zhí)行return,終止生成器函數(shù)的運(yùn)行。

接下來,看看第二段話:

如果當(dāng)這個(gè)方法被調(diào)用時(shí),生成器不在 yield表達(dá)式,那么在傳入值之前,它會(huì)先運(yùn)行到第一個(gè) yield 表達(dá)式。

也就是說不一定用foreach來執(zhí)行生成器函數(shù),send()也可以,直到遇到第一個(gè)yield表達(dá)式,后面步驟就按照第一段話的步驟處理。

2.5.3 Generator::throw()

向生成器中拋入一個(gè)異常。

代碼片段2.4:

throw(new Exception("test"));
}

說明:

執(zhí)行$generator = gen_one_to_three();,這時(shí)不會(huì)執(zhí)行生成器函數(shù)gen_one_to_three()里面的代碼,而是返回一個(gè)生成器對象,也就是說$generator是一個(gè)生成器對象。

foreach ($generator as $value)遍歷生成器對象,因?yàn)镚enerator實(shí)現(xiàn)了Iterator接口,可以用foreach進(jìn)行迭代。這時(shí)就會(huì)調(diào)用生成器函數(shù)gen_one_to_three(),于是執(zhí)行g(shù)en_one_to_three()的代碼。

因?yàn)槭鞘状握{(diào)用,所以從開始執(zhí)行,執(zhí)行for循環(huán),此時(shí)$i=1,執(zhí)行到yield $i;相當(dāng)于生成了一個(gè)值1,并且保存了當(dāng)前的狀態(tài)(比如$i=1、執(zhí)行yield $i;這里)并暫停執(zhí)行。

foreach獲取到這個(gè)值1,并echo輸出。

執(zhí)行$generator->throw(new Exception("test"));,相當(dāng)于在生成器函數(shù)yield $i;處拋出了一個(gè)異常new Exception("test")。

這節(jié)只簡單介紹了生成器類Generator的用法,如果想要實(shí)現(xiàn)更復(fù)雜的功能,比較推薦鳥哥翻譯的《在PHP中使用協(xié)程實(shí)現(xiàn)多任務(wù)調(diào)度》。

0x03 生成器的底層實(shí)現(xiàn)

從前面幾節(jié)我們初步知道生成器函數(shù)跟別的函數(shù)不一樣,普通函數(shù)在返回返回時(shí),除了靜態(tài)變量外其他的都會(huì)被銷毀,下次進(jìn)來還是新的狀態(tài),也就是不會(huì)保存狀態(tài)值,但生成器函數(shù)每次yield是會(huì)保存狀態(tài),包括變量值和運(yùn)行位置,下次調(diào)用時(shí)從上次運(yùn)行的位置后面繼續(xù)運(yùn)行。了解Generator的運(yùn)行機(jī)制,需要對Zend VM有一定了解,可以先閱讀這篇文章《Zend引擎執(zhí)行流程》。

從PHP語法層面分析,底層實(shí)現(xiàn)應(yīng)該具有:

Generator實(shí)現(xiàn)了迭代器接口

生成器函數(shù)調(diào)用時(shí)返回生成器對象

yield后會(huì)保存函數(shù)的局部遍歷和運(yùn)行位置(內(nèi)存不會(huì)被銷毀)

下面,我們從源碼分析Generator的底層實(shí)現(xiàn)。

本節(jié)注意

代碼中// ...表示省略一部分代碼。

代碼中會(huì)加一些注釋說明,以便更好地了解代碼。

Zend/xxx.c:767-864表示Zend目錄下的xxx.c文件,行數(shù)為767至864行。

3.1 Generator類的注冊及其存儲(chǔ)結(jié)構(gòu)

先從數(shù)據(jù)結(jié)構(gòu)入手,類和對象底層的結(jié)構(gòu)分別為:zend_class_entryzend_object。類產(chǎn)生在是編譯時(shí),而對象產(chǎn)生是在運(yùn)行時(shí)。Generator是一個(gè)內(nèi)置類,具有跟其他類共同的性質(zhì),但也有自己不同的特性。

本文不會(huì)介紹類和對象的內(nèi)部實(shí)現(xiàn),感興趣的可以閱讀《面向?qū)ο髮?shí)現(xiàn)-類》和《面向?qū)ο髮?shí)現(xiàn)-對象》。如果你對這些知識(shí)不太了解,請先閱讀上面兩篇文章,以便更好地理解后面的內(nèi)容。

內(nèi)置類在PHP模塊初始化(MINIT)的時(shí)候就注冊了。調(diào)用路徑為:ZEND_MINIT_FUNCTION(core) -> zend_register_default_classes() -> zend_register_generator_ce():

代碼片段3.1.1:

void zend_register_generator_ce(void) /* {{{ */
{
    zend_class_entry ce;

    INIT_CLASS_ENTRY(ce, "Generator", generator_functions); // 初始化Generator類,主要其方法
    zend_ce_generator = zend_register_internal_class(&ce);  // 注冊為內(nèi)部類
    zend_ce_generator->ce_flags |= ZEND_ACC_FINAL; // 設(shè)置為final類,表示不能被繼承。
    /* 下面3個(gè)函數(shù)時(shí)鉤子函數(shù),內(nèi)部類用到,用戶自定義的會(huì)使用默認(rèn)函數(shù) */
    zend_ce_generator->create_object = zend_generator_create; // 創(chuàng)建對象
    zend_ce_generator->serialize = zend_class_serialize_deny; // 序列化,zend_class_serialize_deny表示不能序列化
    zend_ce_generator->unserialize = zend_class_unserialize_deny; // 反序列化,zend_class_unserialize_deny表示不能反序列化

    /* get_iterator has to be assigned *after* implementing the inferface */
    zend_class_implements(zend_ce_generator, 1, zend_ce_iterator); // 實(shí)現(xiàn)zend_ce_iterator類,也就是Iterator
    zend_ce_generator->get_iterator = zend_generator_get_iterator;  // 遍歷方法,這也是個(gè)鉤子方法,用戶自定義的使用默認(rèn)的
    zend_ce_generator->iterator_funcs.funcs = &zend_generator_iterator_functions; // 遍歷相關(guān)的方法(valid/next/current等)使用自己的

    /* 下面幾個(gè)是對象(Generator類的實(shí)例)相關(guān)的 */
    memcpy(&zend_generator_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); // 先使用默認(rèn)的,后面的相應(yīng)覆蓋
    zend_generator_handlers.free_obj = zend_generator_free_storage; // 釋放
    zend_generator_handlers.dtor_obj = zend_generator_dtor_storage; // 銷毀
    zend_generator_handlers.get_gc = zend_generator_get_gc; // 垃圾回收相關(guān)
    zend_generator_handlers.clone_obj = NULL; // 克隆。禁止克隆
    zend_generator_handlers.get_constructor = zend_generator_get_constructor; // 構(gòu)造

    INIT_CLASS_ENTRY(ce, "ClosedGeneratorException", NULL);
    zend_ce_ClosedGeneratorException = zend_register_internal_class_ex(&ce, zend_ce_exception);
}

代碼片段3.1.1可以看出:

Generator類實(shí)現(xiàn)了Iterator接口,但有些方法和Iterator默認(rèn)的方法不太一樣。比如不能序列化/反序列化、遍歷方法(getIterator)不一樣等。

Generator類不能被繼承。

Generator類的實(shí)例不能被克隆等。

3.2 zend_generator結(jié)構(gòu)體

在介紹后面的內(nèi)容之前,我覺得有必要先了解zend_generator這個(gè)結(jié)構(gòu)體,因?yàn)榈讓哟a基本都是圍繞著這個(gè)結(jié)構(gòu)體來開展的。

代碼片段3.2.1:

typedef struct _zend_generator zend_generator;
struct _zend_generator {
    zend_object std;
    zend_object_iterator *iterator;
    /* 生成器函數(shù)的execute_data */
    zend_execute_data *execute_data;
    /* VM stack */
    zend_vm_stack stack;
    /* 當(dāng)前元素的值 */
    zval value;
    /* 當(dāng)前元素的鍵 */
    zval key;
    /* 返回值 */
    zval retval;
    /* 用來保存send()的值 */
    zval *send_target;
    /* 當(dāng)前使用的最大自增key */
    zend_long largest_used_integer_key;
    /* yield from才用到,數(shù)組和非生成器的Traversables類用到,后面會(huì)介紹 */
    zval values;
    /* Node of waiting generators when multiple "yield *" expressions are nested. */
    zend_generator_node node;
    /* Fake execute_data for stacktraces */
    zend_execute_data execute_fake;
    /* 標(biāo)識(shí) */
    zend_uchar flags;
};

重點(diǎn)介紹幾個(gè)重要的:

execute_data:生成器函數(shù)的上下文execute_data,包括當(dāng)前運(yùn)行到的位置、變量等狀態(tài)信息,底層EX宏就是訪問這個(gè)結(jié)構(gòu)的成員。如果這個(gè)為NULL,則表明該生成器已經(jīng)結(jié)束,也就是沒有更多的值生成了。當(dāng)生成器函數(shù)return時(shí)(沒有顯式return底層默認(rèn)return NULL),execute_data變?yōu)镹ULL,后面會(huì)介紹。

vm_stack:VM棧,這個(gè)會(huì)在《3.3 生成器對象的創(chuàng)建》中詳細(xì)介紹。

key:當(dāng)前元素的key,每次yield都會(huì)更新此值,如果yield沒有指定key(也就是yield $key => $value形式),則使用largest_used_integer_key值。

value:當(dāng)前元素的value,也就是生成的值,每次yield都會(huì)更新此值。

retval:生成器的返回值,也就是return返回的值,可以通過Generator::getReturn()獲取。

largest_used_integer_key:存儲(chǔ)當(dāng)前已使用的自增key,yield沒有指定key時(shí)使用下一個(gè)自增值。

send_target:send()的值就存放在這里。

values:yield from委托對象時(shí)用到;yield from生成器不會(huì)存儲(chǔ)在這里,使用后面的node存儲(chǔ)關(guān)系。

node:存儲(chǔ)生成器與其委托對象的關(guān)系,這個(gè)數(shù)據(jù)結(jié)構(gòu)有點(diǎn)復(fù)雜,暫時(shí)不做介紹。

3.3 生成器對象的創(chuàng)建

從生成器語法可以看出,生成器函數(shù)(方法)具有:

必須是個(gè)函數(shù)

函數(shù)有yield關(guān)鍵字

調(diào)用生成器函數(shù)返回生成器對象

3.3.1 編譯階段

先從編譯PHP代碼開始分析,PHP7會(huì)先把PHP代碼編譯成AST(Abstract Syntax Tree,抽象語法生成樹),然后再生成opcode數(shù)組,每條opcode就是一條指令,每條指令都有相應(yīng)的處理函數(shù)(handler)。這里面細(xì)講起來篇幅很長,建議閱讀《PHP代碼的編譯》、《詞法解析、語法解析》和《抽象語法樹編譯流程》這幾篇文章。

先來看第一個(gè)特征:必須是個(gè)函數(shù)。函數(shù)的編譯,比較復(fù)雜,不是本文的重點(diǎn),需要了解可以閱讀《函數(shù)實(shí)現(xiàn)》。函數(shù)的開始先標(biāo)識(shí)CG(active_op_array),展開是compiler_globals.active_op_array,這是一個(gè)zend_op_array結(jié)構(gòu),在PHP中,每一個(gè)也就是獨(dú)立的代碼段(函數(shù)/方法/全局代碼段)都會(huì)編譯成一個(gè)zend_op_array,生成的opcode數(shù)組就存在zend_op_array.opcodes

再來看第二個(gè)特征:函數(shù)有yield關(guān)鍵字。在詞法語法分析階段,如果遇到函數(shù)里面的表達(dá)式有yield,則會(huì)標(biāo)識(shí)為生成器函數(shù)??丛~法語法過程,在Zend/zend_language_parser.y:855:

代碼片段3.3.1:

expr_without_variable:
        T_LIST "(" assignment_list ")" "=" expr
            { $$ = zend_ast_create(ZEND_AST_ASSIGN, $3, $6); }
    |    variable "=" expr
            { $$ = zend_ast_create(ZEND_AST_ASSIGN, $1, $3); }        
// ...

    |    T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); } // 958行 
    |    T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); }
    |    T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); }
    |    T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); }

從定義可以看出yield允許以下三種語法:

yield

yield value

yield key => value

第一種沒有寫返回值,則默認(rèn)返回值為NULL;第二種僅僅返回value,key則為自增的key;第三種返回自定義的key和value。

詞法語法分析器掃描到y(tǒng)ield會(huì)調(diào)用zend_ast_create()函數(shù)(Zend/zend_ast.c:135-144),得到類型(zend_ast->kind)為ZEND_AST_YIELD或者ZEND_AST_YIELD_FROM的zend_ast結(jié)構(gòu)體。從代碼片段3.3.1可以看出:T_YIELD/T_YIELD_FROM會(huì)被當(dāng)成expr_without_variable,也就是表達(dá)式。接著,我們看看表達(dá)式的編譯,在Zend/zend_compile.c:1794的zend_compile_expr()函數(shù):

代碼片段3.3.2:

void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */
{
    /* CG(zend_lineno) = ast->lineno; */
    CG(zend_lineno) = zend_ast_get_lineno(ast);

    switch (ast->kind) {
        case ZEND_AST_ZVAL:
            ZVAL_COPY(&result->u.constant, zend_ast_get_zval(ast));
            result->op_type = IS_CONST;
    // ...
        case ZEND_AST_YIELD: // 7272行
            zend_compile_yield(result, ast);
            return;
        case ZEND_AST_YIELD_FROM:
            zend_compile_yield_from(result, ast);
            return;
    // ...
}
/* }}} */

yield調(diào)用的zend_compile_yield(result, ast)函數(shù),yield from調(diào)用的zend_compile_yield_from(result, ast)函數(shù),這兩個(gè)函數(shù)都會(huì)調(diào)用zend_mark_function_as_generator(),在Zend/zend_compile.c:1145:

代碼片段3.3.3:

static void zend_mark_function_as_generator() /* {{{ */
{
    /* 判斷是不是函數(shù)/方法,不是就報(bào)錯(cuò),也就是yield必須在函數(shù)/方法內(nèi) */
    if (!CG(active_op_array)->function_name) {
        zend_error_noreturn(E_COMPILE_ERROR,
            "The "yield" expression can only be used inside a function");
    }
    
    /* 如果有標(biāo)識(shí)返回類型,則判斷返回類型是否正確,只能是Generator及其父類(Traversable/Iterator) */
    if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
        const char *msg = "Generators may only declare a return type of Generator, Iterator or Traversable, %s is not permitted";
        if (!CG(active_op_array)->arg_info[-1].class_name) {
            zend_error_noreturn(E_COMPILE_ERROR, msg,
                zend_get_type_by_const(CG(active_op_array)->arg_info[-1].type_hint));
        }
        if (!(ZSTR_LEN(CG(active_op_array)->arg_info[-1].class_name) == sizeof("Traversable")-1
                && zend_binary_strcasecmp(ZSTR_VAL(CG(active_op_array)->arg_info[-1].class_name), sizeof("Traversable")-1, "Traversable", sizeof("Traversable")-1) == 0) &&
            !(ZSTR_LEN(CG(active_op_array)->arg_info[-1].class_name) == sizeof("Iterator")-1
                && zend_binary_strcasecmp(ZSTR_VAL(CG(active_op_array)->arg_info[-1].class_name), sizeof("Iterator")-1, "Iterator", sizeof("Iterator")-1) == 0) &&
            !(ZSTR_LEN(CG(active_op_array)->arg_info[-1].class_name) == sizeof("Generator")-1
                && zend_binary_strcasecmp(ZSTR_VAL(CG(active_op_array)->arg_info[-1].class_name), sizeof("Generator")-1, "Generator", sizeof("Generator")-1) == 0)) {
            zend_error_noreturn(E_COMPILE_ERROR, msg, ZSTR_VAL(CG(active_op_array)->arg_info[-1].class_name));
        }
    }

    CG(active_op_array)->fn_flags |= ZEND_ACC_GENERATOR; // 標(biāo)識(shí)函數(shù)是生成器類型?。?!
}
/* }}} */
3.3.2 執(zhí)行階段

前兩個(gè)特征都是在編譯階段,生成器函數(shù)編譯完,得到的opcode為DO_FCALL/DO_FCALL_BY_NAME,解析opcode,得到對應(yīng)的處理函數(shù)(handler)為ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER/ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER,這兩個(gè)函數(shù)對于生成器處理基本是相同的,最終會(huì)調(diào)用zend_generator_create_zval()函數(shù):

代碼片段3.3.4:

ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array *op_array, zval *return_value) /* {{{ */
{
    zend_generator *generator;
    zend_execute_data *current_execute_data;
    zend_execute_data *execute_data;
    zend_vm_stack current_stack = EG(vm_stack); // 保存當(dāng)前的vm_stack,以便后面恢復(fù)

    current_stack->top = EG(vm_stack_top);

    /* 先保存當(dāng)前執(zhí)行的execute_data,后面恢復(fù) */
    current_execute_data = EG(current_execute_data);
    execute_data = zend_create_generator_execute_data(call, op_array, return_value); // 創(chuàng)建新的execute_data
    EG(current_execute_data) = current_execute_data; // 恢復(fù)之前的execute_data

    object_init_ex(return_value, zend_ce_generator); // 實(shí)例化生成器對象,賦給return_value,所以生成器函數(shù)返回的是生成器對象。 

    /* 如果當(dāng)前執(zhí)行的是對象方法,則增加對象的引用計(jì)數(shù) */
    if (Z_OBJ(call->This)) {
        Z_ADDREF(call->This);
    }

    /* 把上面創(chuàng)建新的execute_data,保存到zend_generator */
    generator = (zend_generator *) Z_OBJ_P(return_value);
    generator->execute_data = execute_data;
    generator->stack = EG(vm_stack);
    generator->stack->top = EG(vm_stack_top);
    EG(vm_stack_top) = current_stack->top;
    EG(vm_stack_end) = current_stack->end;
    EG(vm_stack) = current_stack;

    /* 賦值給生成器函數(shù)返回值,真正是zend_generator,為了存儲(chǔ),轉(zhuǎn)為zval類型,后面訪問Generator類的時(shí)候會(huì)介紹 */
    execute_data->return_value = (zval*)generator;

    memset(&generator->execute_fake, 0, sizeof(zend_execute_data));
    Z_OBJ(generator->execute_fake.This) = (zend_object *) generator;
}

通過上面的代碼片段可以知道:生成器調(diào)用時(shí),函數(shù)的返回值返回了一個(gè)生成器對象,這就是上面提到的第三個(gè)特征。另外會(huì)申請自己的VM棧(vm_stack)跟原來的VM棧分離開來,互不干擾,每次執(zhí)行生成器函數(shù)代碼時(shí)只要修改executor_globals(EG)相應(yīng)指針就可以切換到生成器函數(shù)自己的VM棧,這樣就恢復(fù)到了生成器函數(shù)之前的狀態(tài)。通常,execute_data在VM棧上分配(因?yàn)樗鼘?shí)際上不進(jìn)行任何內(nèi)存分配,所以很快)。對于生成器,這不是最理想的,因?yàn)槊看螆?zhí)行被暫?;蚧謴?fù)時(shí)都必須來回復(fù)制(相當(dāng)大)的結(jié)構(gòu)。 這就是為什么對于生成器,使用多帶帶的VM棧分配執(zhí)行上下文,從而允許僅通過替換指針來保存和恢復(fù)它。

3.4 yield生成值

《3.3生成器對象的創(chuàng)建》中提到y(tǒng)ield是一個(gè)表達(dá)式,
編譯的時(shí)候最終會(huì)調(diào)用zend_compile_yield()函數(shù),在Zend/compile.c:6337-6368:

代碼片段 3.4.1:

void zend_compile_yield(znode *result, zend_ast *ast) /* {{{ */
{
    // ...
    /* 編譯key部分 */
    if (key_ast) {
        zend_compile_expr(&key_node, key_ast);
        key_node_ptr = &key_node;
    }
    /* 編譯value部分 */
    if (value_ast) {
        if (returns_by_ref && zend_is_variable(value_ast) && !zend_is_call(value_ast)) {
            zend_compile_var(&value_node, value_ast, BP_VAR_REF);
        } else {
            zend_compile_expr(&value_node, value_ast);
        }
        value_node_ptr = &value_node;
    }
    /* 生成opcode為ZEND_YIELD的zend_op結(jié)構(gòu)體,操作數(shù)1(OP1)為value ,操作數(shù)2(OP2)為key*/
    opline = zend_emit_op(result, ZEND_YIELD, value_node_ptr, key_node_ptr);

    // ...
}

從上面代碼片段可以看出,yield對應(yīng)的opcode是ZEND_YIELD,所以對應(yīng)的處理函數(shù)為ZEND_YIELD_SPEC_{OP1}_{OP2}_HANDLER,生成的處理函數(shù)很多,但是代碼基本都是一樣的,都是由Zend/zend_vm_def.h中的ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMP|VAR|CV|UNUSED)生成的:

第一個(gè)參數(shù)160:ZEND_YIELD宏的值。

第二個(gè)參數(shù)ZEND_YIELD:opcode類型

第三個(gè)參數(shù)CONST|TMP|VAR|CV|UNUSED:表示操作數(shù)1(OP1,也就是值value)可以為這些類型的值。

第四個(gè)參數(shù)CONST|TMP|VAR|CV|UNUSED:表示操作數(shù)2(OP2,也就是鍵key)可以為這些類型的值。

Zend/zend_vm_execute.h(所有處理函數(shù)的存放文件)都是通過執(zhí)行zend_vm_gen.php根據(jù)Zend/zend_vm_def.h的定義生成的。下面我們看一下這個(gè)定義函數(shù):

代碼片段 3.4.2:

ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMP|VAR|CV|UNUSED)
{
    // ...
    /* 先銷毀原來元素的key和value */
    zval_ptr_dtor(&generator->value);
    zval_ptr_dtor(&generator->key);

    /* 這部分是對value部分的處理 */
    if (OP1_TYPE != IS_UNUSED) { // 如果操作數(shù)1類型不是IS_UNUSED,也就是有返回值(yield value這類型)
        if (UNEXPECTED(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
            // 前面一些判斷,基本意思就是把值賦給generator->value,也就是生成值,這里就不貼代碼了
        } else { // 如果不是引用類型
            // 根據(jù)不同的類型,把值賦給generator->value,也就是生成值,這里也不貼代碼了
        }
    } else { // 如果操作數(shù)1類型是IS_UNUSED,也就是沒有返回值(yield這類型),則生成值為NULL
        ZVAL_NULL(&generator->value);
    }

    /* 這部分是對key部分的處理  */
    if (OP2_TYPE != IS_UNUSED) { // 如果操作數(shù)2類型不是IS_UNUSED,也就是有返回自定義的key(yield key => value這類型)
        // 根據(jù)不同的類型,把值賦給generator->key,也就是生成自定義的鍵,這里也不貼代碼了

        /* 如果鍵的值類型為整型(IS_LONG)且大于當(dāng)前自增key(largest_used_integer_key),則修改自增key為鍵的值*/
        if (Z_TYPE(generator->key) == IS_LONG
            && Z_LVAL(generator->key) > generator->largest_used_integer_key
        ) {
            generator->largest_used_integer_key = Z_LVAL(generator->key);
        }
    } else {
        /* 如果沒有自定義key,則把下一個(gè)自增的值賦給key */
        generator->largest_used_integer_key++;
        ZVAL_LONG(&generator->key, generator->largest_used_integer_key);
    }

    if (RETURN_VALUE_USED(opline)) {
        /* If the return value of yield is used set the send
         * target and initialize it to NULL */
        generator->send_target = EX_VAR(opline->result.var);
        ZVAL_NULL(generator->send_target);
    } else {
        generator->send_target = NULL;
    }

    /* 遞增到下個(gè)op,這樣下次繼續(xù)執(zhí)行就可以從下個(gè)op開始執(zhí)行了 */
    ZEND_VM_INC_OPCODE();

    /* The GOTO VM uses a local opline variable. We need to set the opline
     * variable in execute_data so we don"t resume at an old position. */
    SAVE_OPLINE();

    ZEND_VM_RETURN(); // 中斷執(zhí)行
}

從上面代碼片段可以看出:yield首先生成鍵和值(本質(zhì)就是修改zend_generator的key和value),生成完鍵值后保存狀態(tài),然后中斷生成器函數(shù)的執(zhí)行。

3.5 生成器對象的訪問

前面兩節(jié)介紹了Generator類和生成器對象的結(jié)構(gòu)及創(chuàng)建,我們知道生成器對象可以通過foreach訪問,也可以多帶帶調(diào)用生成器對象接口訪問。本節(jié)介紹這兩種方式訪問生成器對象的底層實(shí)現(xiàn),兩種訪問方式都是圍繞zend_generator這個(gè)結(jié)構(gòu)開展。

3.5.1 使用生成器對象接口訪問

前面《2.4 Generator類》已經(jīng)提到過Generator類實(shí)現(xiàn)了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 )
}

對應(yīng)C代碼的函數(shù)如下:

rewind  -> ZEND_METHOD(Generator, rewind)
key     -> ZEND_METHOD(Generator, key)
next    -> ZEND_METHOD(Generator, next)
current -> ZEND_METHOD(Generator, current)
valid   -> ZEND_METHOD(Generator, valid)
send    -> ZEND_METHOD(Generator, send)
throw   -> ZEND_METHOD(Generator, throw)

ZEND_METHOD是內(nèi)核定義的一個(gè)宏,方便閱讀和開發(fā),這里不做介紹,底層代碼都在Zend/zend_generators.c:767-864。

3.5.1.1 ZEND_METHOD(Generator, rewind)

ZEND_METHOD(Generator, rewind)
代碼片段3.5.1:

ZEND_METHOD(Generator, rewind)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());
    zend_generator_rewind(generator);
}

Z_OBJ_P(getThis()),展開來是(*(&execute_data.This)).value.obj, 獲取的是當(dāng)前execute_data.This這個(gè)zval(類型為object)的object值(zval.value)的地址。但是這里強(qiáng)行轉(zhuǎn)換是不是覺得很奇怪?

還記得代碼片段3.3.6中提到:

object_init_ex(return_value, zend_ce_generator); // 實(shí)例化生成器對象,賦給return_value,所以生成器函數(shù)返回的是生成器對象。

初始化函數(shù)object_init_ex()最終會(huì)調(diào)用_object_and_properties_init()函數(shù),在Zend/zend_API.c:1275-1310:

代碼片段3.5.2:

ZEND_API int _object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties ZEND_FILE_LINE_DC) /* {{{ */
{
    // ...
    if (class_type->create_object == NULL) {
        ZVAL_OBJ(arg, zend_objects_new(class_type));
        if (properties) {
            object_properties_init_ex(Z_OBJ_P(arg), properties);
        } else {
            object_properties_init(Z_OBJ_P(arg), class_type);
        }
    } else {
        ZVAL_OBJ(arg, class_type->create_object(class_type));
    }
    return SUCCESS;
}
/* }}} */

代碼片段3.4.2可以看出,如果zend_class_entry定義有create_object()函數(shù),那么會(huì)調(diào)用create_object()函數(shù)。而zend_ce_generator是有定義有create_object()函數(shù),該函數(shù)為zend_generator_create(),參見《3.1 Generator類的注冊及其存儲(chǔ)結(jié)構(gòu)》:

代碼片段3.5.3:

static zend_object *zend_generator_create(zend_class_entry *class_type) /* {{{ */
{
    // ... 
    generator = emalloc(sizeof(zend_generator));
    memset(generator, 0, sizeof(zend_generator));
    // ...
    return (zend_object*)generator;
}
/* }}} */

內(nèi)存里存儲(chǔ)的是zend_generator,后面強(qiáng)制轉(zhuǎn)換為zend_object,因?yàn)榉祷刂狄莦val類型,所以這里做了強(qiáng)制轉(zhuǎn)換。這就能解釋為什么可以generator = (zend_generator *) Z_OBJ_P(getThis())

回到正題,ZEND_METHOD(Generator, rewind)得到zend_generator后,調(diào)用zend_generator_rewind()

代碼片段3.5.4:

static void inline zend_generator_rewind(zend_generator *generator)
{
    zend_generator_ensure_initialized(generator); // 保證generator已經(jīng)初始化過了
    /* 如果已經(jīng)yield過了,就不能再rewind */
    if (!(generator->flags & ZEND_GENERATOR_AT_FIRST_YIELD)) {
        zend_throw_exception(NULL, "Cannot rewind a generator that was already run", 0);
    }
}

如果yield過了,則不能再rewind,也就是不能再用foreach遍歷,因?yàn)閒oreach也會(huì)調(diào)用rewind,這個(gè)后面再介紹。

3.5.1.2 ZEND_METHOD(Generator, valid)

ZEND_METHOD(Generator, valid),檢查當(dāng)前位置是否有效,如果無效,foreach會(huì)停止遍歷。

代碼片段3.5.5:

ZEND_METHOD(Generator, valid)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());

    zend_generator_ensure_initialized(generator);

    zend_generator_get_current(generator);

    RETURN_BOOL(EXPECTED(generator->execute_data != NULL));
}

valid也是獲取到zend_generator后,調(diào)用zend_generator_get_current()函數(shù),獲取當(dāng)前需要運(yùn)行的zend_generator,然后判斷為NULL,以此已經(jīng)更多的值生成了,這在《3.2 zend_generator結(jié)構(gòu)體》中詳細(xì)說明過。

3.5.1.3 ZEND_METHOD(Generator, current)

ZEND_METHOD(Generator, current)獲取當(dāng)前元素的值。

代碼片段3.5.6:

ZEND_METHOD(Generator, current)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());

    zend_generator_ensure_initialized(generator);

    root = zend_generator_get_current(generator);
    if (EXPECTED(generator->execute_data != NULL && Z_TYPE(root->value) != IS_UNDEF)) {
        zval *value = &root->value;

        ZVAL_DEREF(value);
        ZVAL_COPY(return_value, value);
    }
}

和valid方法一樣,也是先獲取到zend_generator,然后判斷生成器函數(shù)是否結(jié)束(generator->execute_data != NULL)并且有值(Z_TYPE(root->value) != IS_UNDEF),然后把值返回。

3.5.1.4 ZEND_METHOD(Generator, key)

ZEND_METHOD(Generator, key)獲取當(dāng)前元素的鍵,也就是yield生成值時(shí)的key,沒有指定會(huì)使用自增的key,即zend_generator.largest_used_integer_key

代碼片段3.5.7:

ZEND_METHOD(Generator, key)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());

    zend_generator_ensure_initialized(generator);

    root = zend_generator_get_current(generator);
    if (EXPECTED(generator->execute_data != NULL && Z_TYPE(root->key) != IS_UNDEF)) {
        zval *key = &root->key;

        ZVAL_DEREF(key);
        ZVAL_COPY(return_value, key);
    }
}

ZEND_METHOD(Generator, value)差不多,zend_generator.key存儲(chǔ)的就是當(dāng)前元素的鍵,這在《3.2 zend_generator結(jié)構(gòu)體》中詳細(xì)說明過。

3.5.1.5 ZEND_METHOD(Generator, next)

ZEND_METHOD(Generator, next)向前移動(dòng)到下一個(gè)元素,也就是執(zhí)行到下一個(gè)yield *。

代碼片段3.5.8:

ZEND_METHOD(Generator, next)
{
    // ...
    generator = (zend_generator *) Z_OBJ_P(getThis());

    zend_generator_ensure_initialized(generator);

    zend_generator_resume(generator);
}

主要分析zend_generator_resume()函數(shù),這個(gè)函數(shù)比較重要:

代碼片段3.5.9:

ZEND_API void zend_generator_resume(zend_generator *orig_generator) 
{
    zend_generator *generator = zend_generator_get_current(orig_generator); // 獲取要執(zhí)行生成器

    /* 如果生成器函數(shù)已經(jīng)結(jié)束,則直接返回,不能繼續(xù)執(zhí)行 */
    if (UNEXPECTED(!generator->execute_data)) {
        return;
    }

try_again: // 這個(gè)標(biāo)簽是個(gè)yield from用的,解析完yield from表達(dá)式,需要生成(yield)一個(gè)值。
    /* 如果有ZEND_GENERATOR_CURRENTLY_RUNNING標(biāo)識(shí),則表示已經(jīng)運(yùn)行,已經(jīng)運(yùn)行的不能再調(diào)用這方法繼續(xù)運(yùn)行 */
    if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
        zend_throw_error(NULL, "Cannot resume an already running generator");
        return;
    }

    if (UNEXPECTED((orig_generator->flags & ZEND_GENERATOR_DO_INIT) != 0 && !Z_ISUNDEF(generator->value))) {
        /* We must not advance Generator if we yield from a Generator being currently run */
        return;
    }
    /* 如果values有值,說明是非生成器類的委托對象產(chǎn)生(yield from)的 */
    if (UNEXPECTED(!Z_ISUNDEF(generator->values))) {
        if (EXPECTED(zend_generator_get_next_delegated_value(generator) == SUCCESS)) { // 委托對象有值則直接返回
            return;
        }
        /* yield from沒有更多值生成,則繼續(xù)運(yùn)行生成器函數(shù)后面的代碼 */
    }

    /* Drop the AT_FIRST_YIELD flag */
    orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;

    {
        /* 保存當(dāng)前執(zhí)行的execute_data上下文和VM棧,以便后面恢復(fù),這在前面已經(jīng)介紹過了 */
        zend_execute_data *original_execute_data = EG(current_execute_data);
        zend_class_entry *original_scope = EG(scope);
        zend_vm_stack original_stack = EG(vm_stack);
        original_stack->top = EG(vm_stack_top);

        /* 修改執(zhí)行器的指針,指向要運(yùn)行的生成器函數(shù)和其相應(yīng)的VM棧 */
        EG(current_execute_data) = generator->execute_data;
        EG(scope) = generator->execute_data->func->common.scope;
        EG(vm_stack_top) = generator->stack->top;
        EG(vm_stack_end) = generator->stack->end;
        EG(vm_stack) = generator->stack;

        // ...

        /* 執(zhí)行生成器函數(shù)的代碼 */
        generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
        zend_execute_ex(generator->execute_data); // 執(zhí)行,遇到y(tǒng)ield停止繼續(xù)執(zhí)行
        generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;

        /* 修改VM棧相關(guān)的指針,因?yàn)樯厦孢\(yùn)行過程中,VM棧不夠,會(huì)重新申請新的MV棧,所以需要修改相關(guān)指針 */
        if (EXPECTED(generator->execute_data)) {
            generator->stack = EG(vm_stack);
            generator->stack->top = EG(vm_stack_top);
        }

        /* 恢復(fù)原來保存的execute_data上下文和VM棧 */
        EG(current_execute_data) = original_execute_data;
        EG(scope) = original_scope;
        EG(vm_stack_top) = original_stack->top;
        EG(vm_stack_end) = original_stack->end;
        EG(vm_stack) = original_stack;

        /* 處理異常,后面介紹throw()方法時(shí)再講 */
        if (UNEXPECTED(EG(exception) != NULL)) {
            if (generator == orig_generator) {
                zend_generator_close(generator, 0);
                zend_throw_exception_internal(NULL);
            } else {
                generator = zend_generator_get_current(orig_generator);
                zend_generator_throw_exception(generator, NULL);
                goto try_again;
            }
        }

        /* yiled from沒有生成值時(shí),要重新進(jìn)入(try_again)生成值 */
        if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && (generator->execute_data->opline - 1)->opcode == ZEND_YIELD_FROM))) {
            generator = zend_generator_get_current(orig_generator);
            goto try_again;
        }
    }
}

zend_generator_resume()函數(shù),表面意思就是繼續(xù)運(yùn)行生成器函數(shù)。前面是一些判斷,然后保存當(dāng)前上下文,執(zhí)行生成器代碼,遇到y(tǒng)ield返回,然后恢復(fù)上下文。

3.5.1.6 ZEND_METHOD(Generator, send)

(未完成)

3.5.1.7 ZEND_METHOD(Generator, throw)

(未完成)

3.5.2 使用foreach訪問

foreach訪問生成器對象,其實(shí)就是調(diào)用zend_ce_generator->get_iterator,這在《3.1Generator類的注冊及其存儲(chǔ)結(jié)構(gòu)》中介紹過,這是一個(gè)鉤子,生成器用的是zend_generator_get_iterator,在Zend/zend_generators.c:1069-1093:

代碼片段3.5.10:

zend_object_iterator *zend_generator_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
{
    zend_object_iterator *iterator;
    zend_generator *generator = (zend_generator*)Z_OBJ_P(object);
    // ...
    zend_iterator_init(iterator); // 初始化

    iterator->funcs = &zend_generator_iterator_functions; //設(shè)置迭代器對象的相關(guān)處理函數(shù)
    ZVAL_COPY(&iterator->data, object); // 把zend_generator賦給iterator的data,后面會(huì)用到

    return iterator;
}
/* }}} */

zend_generator_get_iterator()把迭代器對象的相關(guān)處理函數(shù)設(shè)置為zend_generator_iterator_functions,使得迭代生成器對象是使用相應(yīng)的自定義函數(shù),主要函數(shù)有:

代碼片段3.5.11:

zend_generator_iterator_valid()         // 判斷當(dāng)前位置是否有效
zend_generator_iterator_get_data()      // 獲取當(dāng)前元素的值
zend_generator_iterator_get_key()       // 獲取當(dāng)前元素的鍵
zend_generator_iterator_move_forward()  // 向前移動(dòng)到下一個(gè)元素
zend_generator_iterator_rewind()        // 指向第一個(gè)元素

函數(shù)細(xì)節(jié)就不一一介紹了,跟《3.5.1 使用生成器對象接口訪問》的相應(yīng)函數(shù)差不多的。這里我們僅僅分析zend_generator_iterator_rewind()函數(shù),其他的都類似:

代碼片段3.5.12:

static void zend_generator_iterator_rewind(zend_object_iterator *iterator) /* {{{ */
{
    zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);

    zend_generator_rewind(generator);
}

因?yàn)樵诔跏蓟臅r(shí)候已經(jīng)把zend_generator賦給iterator->data,詳見代碼片段3.5.10,所以這里可以從iterator拿到zend_generator對象,其他幾個(gè)函數(shù)亦是如此。zend_generator_rewind()函數(shù)在ZEND_METHOD(Generator, rewind)已經(jīng)介紹過了,這里就不多說了。

3.6 生成器的終止

從生成器語法我們知道:return語句會(huì)終止生成器的執(zhí)行,如果沒有顯式return,則默認(rèn)會(huì)在結(jié)束return null。生成器里面的return語句的opcode是ZEND_GENERATOR_RETURN,而return語句的opcode應(yīng)該是ZEND_RETURN,這個(gè)處理是pass_two()函數(shù)里:

代碼片段3.6.1

ZEND_API int pass_two(zend_op_array *op_array)
{
    // ...
    opline = op_array->opcodes;
    end = opline + op_array->last;
    while (opline < end) {
        switch (opline->opcode) {
            case ZEND_RETURN:
            case ZEND_RETURN_BY_REF:
                if (op_array->fn_flags & ZEND_ACC_GENERATOR) {
                    opline->opcode = ZEND_GENERATOR_RETURN;
                }
                break;
        }
        // ...
    }
    // ...
}

從上面代碼可以看出,如果是生成器函數(shù)里面的return則把opcode由ZEND_RETURN修改為ZEND_GENERATOR_RETURN,對應(yīng)的處理函數(shù)定義為ZEND_VM_HANDLER(161, ZEND_GENERATOR_RETURN, CONST|TMP|VAR|CV, ANY)

ZEND_VM_HANDLER(161, ZEND_GENERATOR_RETURN, CONST|TMP|VAR|CV, ANY)
{
    // ...
    zend_generator *generator = zend_get_running_generator(execute_data); // 獲取當(dāng)前運(yùn)行的生成器函數(shù)
    // ...
    retval = GET_OP1_ZVAL_PTR(BP_VAR_R);
    /* 不同操作值類型不同處理,但都是賦給給retval,后面可以使用getReturn()方法獲取返回值 */
    if (OP1_TYPE == IS_CONST || OP1_TYPE == IS_TMP_VAR) {
        ZVAL_COPY_VALUE(&generator->retval, retval);
        // ... 
    } else if (OP1_TYPE == IS_CV) {
        ZVAL_DEREF(retval);
        ZVAL_COPY(&generator->retval, retval);
    } else /* if (OP1_TYPE == IS_VAR) */ {
        if (UNEXPECTED(Z_ISREF_P(retval))) {
            // ...
            ZVAL_COPY_VALUE(&generator->retval, retval);
            // ...
        } else {
            ZVAL_COPY_VALUE(&generator->retval, retval); // 
        }
    }

    /* 關(guān)閉生成器,釋放資源(包括申請的VM棧) */
    zend_generator_close(generator, 1);

    /* 執(zhí)行器返回 */
    ZEND_VM_RETURN();
}

前面是根據(jù)不同類型,把值賦給retval,后面調(diào)用zend_generator_close()關(guān)閉生成器,釋放資源,我們來看看這個(gè)函數(shù):

ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished_execution) /* {{{ */
{
    if (EXPECTED(generator->execute_data)) {
        zend_execute_data *execute_data = generator->execute_data;
        // ...
        /* 生成器函數(shù)執(zhí)行過程中出現(xiàn)了致命錯(cuò)誤,也會(huì)執(zhí)行zend_generator_close(). 但是為啥后面的語句不執(zhí)行暫時(shí)還不清楚 */
        if (UNEXPECTED(CG(unclean_shutdown))) {
            generator->execute_data = NULL;
            return;
        }

        zend_vm_stack_free_extra_args(generator->execute_data); // 釋放額外的參數(shù),也就是參數(shù)列表之外的
        /* return語句的清理工作 */
        if (UNEXPECTED(!finished_execution)) {
            zend_generator_cleanup_unfinished_execution(generator, 0);
        }

        // ...
        efree(generator->stack); // 釋放申請的VM棧
        generator->execute_data = NULL; // 把execute_data賦值為NULL,這樣isValid()就返回FALSE.
    }
}
3.7 小結(jié)

生成器底層實(shí)現(xiàn)僅介紹了yield部分實(shí)現(xiàn),包括yield生成值、生成器的訪問以及生成器的終止。底層實(shí)現(xiàn)還是很好理解的,基本圍繞著zend_generator結(jié)構(gòu)體進(jìn)行。yield from部分較復(fù)雜,目前尚未分析清楚,有興趣的同學(xué)可以分析一下。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/29751.html

相關(guān)文章

  • PHP回顧之協(xié)程

    摘要:本文先回顧生成器,然后過渡到協(xié)程編程。其作用主要體現(xiàn)在三個(gè)方面數(shù)據(jù)生成生產(chǎn)者,通過返回?cái)?shù)據(jù)數(shù)據(jù)消費(fèi)消費(fèi)者,消費(fèi)傳來的數(shù)據(jù)實(shí)現(xiàn)協(xié)程。解決回調(diào)地獄的方式主要有兩種和協(xié)程。重點(diǎn)應(yīng)當(dāng)關(guān)注控制權(quán)轉(zhuǎn)讓的時(shí)機(jī),以及協(xié)程的運(yùn)作方式。 轉(zhuǎn)載請注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請求 cookie web響應(yīng) sess...

    Java3y 評論0 收藏0
  • PHP 成器入門

    摘要:執(zhí)行語句的唯一目的是結(jié)束生成器執(zhí)行。這就是需要生成器需要有返回值的意義,這也是為何我們將這個(gè)特性加入到中的原因,我們會(huì)將最后執(zhí)行的值作為返回值,但這不是一個(gè)好的解決方案。 本文首發(fā)于 入門 PHP 生成器,轉(zhuǎn)載請注明出處。 PHP 在 5.5 版本中引入了「生成器(Generator)」特性,不過這個(gè)特性并沒有引起人們的注意。在官方的 從 PHP 5.4.x 遷移到 PHP 5.5.x...

    IamDLY 評論0 收藏0
  • PHP異步嘗試一:初識(shí)成器

    摘要:下的異步嘗試系列下的異步嘗試一初識(shí)生成器下的異步嘗試二初識(shí)協(xié)程下的異步嘗試三協(xié)程的版自動(dòng)執(zhí)行器下的異步嘗試四版的下的異步嘗試五版的的繼續(xù)完善生成器類獲取迭代器當(dāng)前值獲取迭代器當(dāng)前值返回當(dāng)前產(chǎn)生的鍵生成器從上一次處繼續(xù)執(zhí)行重置迭代器向生成器中 PHP下的異步嘗試系列 PHP下的異步嘗試一:初識(shí)生成器 PHP下的異步嘗試二:初識(shí)協(xié)程 PHP下的異步嘗試三:協(xié)程的PHP版thunkify自...

    tomorrowwu 評論0 收藏0
  • PHP 7 值得期待新特性(下)

    摘要:在本系列的第一篇我們介紹了中最重要的一些不兼容性修復(fù)以及兩大新特性。例如這個(gè)綠色的心形,,可以表示為字符串。雖然現(xiàn)在它只具備內(nèi)部測試品質(zhì)目前已可以下載,但的確讓人期待。向項(xiàng)目報(bào)告錯(cuò)誤,并定期重試。 這是我們期待已久的 PHP 7 系列文章的第二篇。點(diǎn)此閱讀 第一篇本文系 OneAPM 工程師編譯整理。 也許你已經(jīng)知道,重頭戲 PHP 7 的發(fā)布將在今年到來!現(xiàn)在,讓我們來了解一下,新版...

    BetaRabbit 評論0 收藏0
  • Swagger 生成 PHP restful API 接口文檔

    摘要:需求和背景需求為客戶端同事寫接口文檔的各位后端同學(xué)已經(jīng)在各種場合回憶了使用自動(dòng)化文檔工具前手寫文檔的血淚史我的故事卻又不同因?yàn)槭紫葋碚f我在公司是組負(fù)責(zé)人屬于上述血淚史中催死人不償命的客戶端陣營但血淚史卻是相通的沒有自動(dòng)化文檔的日子對接口就是 需求和背景 需求: 為客戶端同事寫接口文檔的各位后端同學(xué),已經(jīng)在各種場合回憶了使用自動(dòng)化文檔工具前手寫文檔的血淚史.我的故事卻又不同,因?yàn)槭紫葋碚f...

    xiaotianyi 評論0 收藏0

發(fā)表評論

0條評論

LMou

|高級講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<