本文主要介紹:php
本文比較長,可能會耗費你比較多的時間。若是你比較瞭解Generator的用法,僅想了解底層實現,能夠直接跳到底層實現部分。html
本文分析的PHP源碼版本爲:7.0.29。node
2.3 生成器語法github
2.5 Generator方法express
0x03 生成器的底層實現數組
此文爲我的的學習筆記,意在對本身的學習過程進行總結。因爲我的能力有限,錯漏在所不免,歡迎批評指正。
Generator,中文翻譯:生成器,是PHP 5.5開始支持的語法。ide
生成器提供了一種更容易的方法來實現簡單的對象迭代,相比較定義類實現 Iterator 接口的方式,性能開銷和複雜性大大下降。生成器容許你在 foreach 代碼塊中寫代碼來迭代一組數據而不須要在內存中建立一個數組, 那會使你的內存達到上限,或者會佔據可觀的處理時間。相反,你能夠寫一個生成器函數,就像一個普通的自定義函數同樣, 和普通函數只返回一次不一樣的是, 生成器能夠根據須要 yield 屢次,以便生成須要迭代的值。
當一個生成器的函數的被調用時,對返回內置類Generator的一個實例化對象。這個對象實現了Iterator接口,跟迭代器同樣能夠向前迭代,而且提供了維護這個對象的狀態的接口,包括向它發送值和從它接收值。
一個生成器函數看起來像一個普通的函數,不一樣的是普通函數返回一個值,而一個生成器能夠yield生成許多它所須要的值。當一個生成器被調用的時候,它返回一個能夠被遍歷的對象.當你遍歷這個對象的時候(例如經過一個foreach循環),PHP 將會在每次須要值的時候調用生成器函數,並在產生一個值以後保存生成器的狀態,這樣它就能夠在須要產生下一個值的時候恢復調用狀態。
一旦再也不須要產生更多的值,生成器函數能夠簡單退出,而調用生成器的代碼還能夠繼續執行,就像一個數組已經被遍歷完了
PHP 5是不能夠有返回值的,若是這樣作會致使編譯錯誤。可是一個空的return語句是能夠的,這會終止生成器的執行。PHP 7支持返回值,使用Generator::getReturn()獲取返回值。
生成器函數的核心是yield關鍵字。它最簡單的調用形式看起來像一個return申明,不一樣之處在於普通return會返回值並終止函數的執行,而yield會返回一個值給循環調用今生成器的代碼而且只是暫停執行生成器函數。
理論顯得空洞無力,show me your code,那就來看一段簡單的代碼,以便更容易理解生成器語法:
代碼片斷2.1:
<?php function gen_one_to_three() { for ($i = 1; $i <= 3; $i++) { yield $i; } } $generator = gen_one_to_three(); foreach ($generator as $value) { echo "$value\n"; }
說明:
$generator = gen_one_to_three();
,這時不會執行生成器函數gen_one_to_three()裏面的代碼,而是返回一個生成器對象,也就是說$generator是一個生成器對象。foreach ($generator as $value)
遍歷生成器對象,由於Generator實現了Iterator接口,能夠用foreach進行迭代。這時就會調用生成器函數gen_one_to_three(),因而執行gen_one_to_three()的代碼。yield $i;
至關於生成了一個值1,而且保存了當前的狀態(好比$i=一、執行yield $i;
這裏)並暫停執行。$i++
,這是$i=2。yield $i;
至關於生成一個值2,而且保存了當前的狀態並暫停執行。$i++
,$i=3,執行yield $i;
生成一個值3給$value並輸出$value。PHP 7容許您使用yield from關鍵字從另外一個生成器、Traversable對象或數組中生成值(後面簡稱 委託對象),這叫生成器委託。 生成器將從內嵌生成器、對象或數組中生成全部值,直到它再也不有效,而後繼續生成器的執行。
代碼片斷2.3.2:
<?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幫助手冊,例子也基原本自手冊,我只是加了一些說明,以便幫助更好的理解其語法。
前面說Generator類實現了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()
方法。
既然實現了Iterator接口,那上面的代碼片斷2.3.1也能夠改爲下面的,執行結果同樣的:
代碼片斷2.4.1:
<?php function gen_one_to_three() { for ($i = 1; $i <= 3; $i++) { yield $i; } } $generator = gen_one_to_three(); while ($generator->valid()) { echo "{$generator->current()}\n"; $generator->next(); }
這是一個魔術方法,當一個對象被反序列化時會調用,但生成器對象不能被序列化和反序列化,因此__wakeup()
方法拋出一個異常以表示生成器不能被序列化。
前面生成器對象部分提到:能夠從生成器對象接收值和向它發送值。yield就是從它接收值,那發送值是什麼呢?就是這個send()方法。
public mixed Generator::send ( mixed $value )
向生成器中傳入一個值,而且當作 yield 表達式的結果,而後繼續執行生成器。若是當這個方法被調用時,生成器不在 yield表達式,那麼在傳入值以前,它會先運行到第一個 yield 表達式.
先來理解第一段話:
向生成器中傳入一個值,而且當作 yield 表達式的結果,而後繼續執行生成器。
yield後生成了值,還能夠用這個生成器對象的send()方法發送一個值,而這個值做爲表達式的結果,而後在生成器函數裏面能夠獲取到這個值,接着繼續執行生成器。看下面的代碼:
代碼片斷2.5.1:
<?php function gen_one_to_three() { for ($i = 1; $i <= 3; $i++) { $cmd = (yield $i); if ($cmd === 'exit') { return; } } } $generator = gen_one_to_three(); foreach ($generator as $value) { echo "$value\n"; $generator->send('exit'); }
說明:
$generator = gen_one_to_three();
,這時不會執行生成器函數gen_one_to_three()裏面的代碼,而是返回一個生成器對象,也就是說$generator是一個生成器對象。foreach ($generator as $value)
遍歷生成器對象,由於Generator實現了Iterator接口,能夠用foreach進行迭代。這時就會調用生成器函數gen_one_to_three(),因而執行gen_one_to_three()的代碼。$cmd = (yield $i);
至關於生成了一個值1,而且保存了當前的狀態(好比$i=一、執行yield $i;
這裏)並暫停執行。$generator->send('exit');
向生成器函數裏面發送值"exit"。yield $i;
表達式的值,而後賦給$cmd,也就是$cmd = (yield $i);
至關於$cmd = "exit";
,繼續執行生成器函數。if ($cmd === 'exit')
條件成立,因此執行return,終止生成器函數的運行。接下來,看看第二段話:
若是當這個方法被調用時,生成器不在 yield表達式,那麼在傳入值以前,它會先運行到第一個 yield 表達式。
也就是說不必定用foreach來執行生成器函數,send()也能夠,直到遇到第一個yield表達式,後面步驟就按照第一段話的步驟處理。
向生成器中拋入一個異常。
代碼片斷2.4:
<?php function gen_one_to_three() { for ($i = 1; $i <= 3; $i++) { yield $i; } } $generator = gen_one_to_three(); foreach ($generator as $value) { echo "$value\n"; $generator->throw(new \Exception('test')); }
說明:
$generator = gen_one_to_three();
,這時不會執行生成器函數gen_one_to_three()裏面的代碼,而是返回一個生成器對象,也就是說$generator是一個生成器對象。foreach ($generator as $value)
遍歷生成器對象,由於Generator實現了Iterator接口,能夠用foreach進行迭代。這時就會調用生成器函數gen_one_to_three(),因而執行gen_one_to_three()的代碼。yield $i;
至關於生成了一個值1,而且保存了當前的狀態(好比$i=一、執行yield $i;
這裏)並暫停執行。$generator->throw(new \Exception('test'));
,至關於在生成器函數yield $i;
處拋出了一個異常new \Exception('test')
。這節只簡單介紹了生成器類Generator的用法,若是想要實現更復雜的功能,比較推薦鳥哥翻譯的《在PHP中使用協程實現多任務調度》。
從前面幾節咱們初步知道生成器函數跟別的函數不同,普通函數在返回返回時,除了靜態變量外其餘的都會被銷燬,下次進來仍是新的狀態,也就是不會保存狀態值,但生成器函數每次yield是會保存狀態,包括變量值和運行位置,下次調用時從上次運行的位置後面繼續運行。瞭解Generator的運行機制,須要對Zend VM有必定了解,能夠先閱讀這篇文章《Zend引擎執行流程》。
從PHP語法層面分析,底層實現應該具備:
下面,咱們從源碼分析Generator的底層實現。
本節注意:
// ...
表示省略一部分代碼。先從數據結構入手,類和對象底層的結構分別爲:zend_class_entry
和zend_object
。類產生在是編譯時,而對象產生是在運行時。Generator是一個內置類,具備跟其餘類共同的性質,但也有本身不一樣的特性。
本文不會介紹類和對象的內部實現,感興趣的能夠閱讀《面向對象實現-類》和《面向對象實現-對象》。若是你對這些知識不太瞭解,請先閱讀上面兩篇文章,以便更好地理解後面的內容。
內置類在PHP模塊初始化(MINIT)的時候就註冊了。調用路徑爲: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); // 註冊爲內部類 zend_ce_generator->ce_flags |= ZEND_ACC_FINAL; // 設置爲final類,表示不能被繼承。 /* 下面3個函數時鉤子函數,內部類用到,用戶自定義的會使用默認函數 */ zend_ce_generator->create_object = zend_generator_create; // 建立對象 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); // 實現zend_ce_iterator類,也就是Iterator zend_ce_generator->get_iterator = zend_generator_get_iterator; // 遍歷方法,這也是個鉤子方法,用戶自定義的使用默認的 zend_ce_generator->iterator_funcs.funcs = &zend_generator_iterator_functions; // 遍歷相關的方法(valid/next/current等)使用本身的 /* 下面幾個是對象(Generator類的實例)相關的 */ memcpy(&zend_generator_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); // 先使用默認的,後面的相應覆蓋 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; // 垃圾回收相關 zend_generator_handlers.clone_obj = NULL; // 克隆。禁止克隆 zend_generator_handlers.get_constructor = zend_generator_get_constructor; // 構造 INIT_CLASS_ENTRY(ce, "ClosedGeneratorException", NULL); zend_ce_ClosedGeneratorException = zend_register_internal_class_ex(&ce, zend_ce_exception); }
從代碼片斷3.1.1能夠看出:
在介紹後面的內容以前,我以爲有必要先了解zend_generator這個結構體,由於底層代碼基本都是圍繞着這個結構體來開展的。
代碼片斷3.2.1:
typedef struct _zend_generator zend_generator; struct _zend_generator { zend_object std; zend_object_iterator *iterator; /* 生成器函數的execute_data */ zend_execute_data *execute_data; /* VM stack */ zend_vm_stack stack; /* 當前元素的值 */ zval value; /* 當前元素的鍵 */ zval key; /* 返回值 */ zval retval; /* 用來保存send()的值 */ zval *send_target; /* 當前使用的最大自增key */ zend_long largest_used_integer_key; /* yield from纔用到,數組和非生成器的Traversables類用到,後面會介紹 */ 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; /* 標識 */ zend_uchar flags; };
重點介紹幾個重要的:
從生成器語法能夠看出,生成器函數(方法)具備:
先從編譯PHP代碼開始分析,PHP7會先把PHP代碼編譯成AST(Abstract Syntax Tree,抽象語法生成樹),而後再生成opcode數組,每條opcode就是一條指令,每條指令都有相應的處理函數(handler)。這裏面細講起來篇幅很長,建議閱讀《PHP代碼的編譯》、《詞法解析、語法解析》和《抽象語法樹編譯流程》這幾篇文章。
先來看第一個特徵:必須是個函數。函數的編譯,比較複雜,不是本文的重點,須要瞭解能夠閱讀《函數實現》。函數的開始先標識CG(active_op_array)
,展開是compiler_globals.active_op_array
,這是一個zend_op_array
結構,在PHP中,每個也就是獨立的代碼段(函數/方法/全局代碼段)都會編譯成一個zend_op_array
,生成的opcode數組就存在zend_op_array.opcodes
。
再來看第二個特徵:函數有yield關鍵字。在詞法語法分析階段,若是遇到函數裏面的表達式有yield,則會標識爲生成器函數。看詞法語法過程,在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容許如下三種語法:
第一種沒有寫返回值,則默認返回值爲NULL;第二種僅僅返回value,key則爲自增的key;第三種返回自定義的key和value。
詞法語法分析器掃描到yield會調用zend_ast_create()
函數(Zend/zend_ast.c:135-144),獲得類型(zend_ast->kind)爲ZEND_AST_YIELD
或者ZEND_AST_YIELD_FROM
的zend_ast結構體。從代碼片斷3.3.1能夠看出:T_YIELD/T_YIELD_FROM
會被當成expr_without_variable
,也就是表達式。接着,咱們看看錶達式的編譯,在Zend/zend_compile.c:1794的zend_compile_expr()
函數:
代碼片斷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調用的zend_compile_yield(result, ast)
函數,yield from調用的zend_compile_yield_from(result, ast)
函數,這兩個函數都會調用zend_mark_function_as_generator()
,在Zend/zend_compile.c:1145:
代碼片斷3.3.3:
static void zend_mark_function_as_generator() /* {{{ */ { /* 判斷是否是函數/方法,不是就報錯,也就是yield必須在函數/方法內 */ if (!CG(active_op_array)->function_name) { zend_error_noreturn(E_COMPILE_ERROR, "The \"yield\" expression can only be used inside a function"); } /* 若是有標識返回類型,則判斷返回類型是否正確,只能是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; // 標識函數是生成器類型!!! } /* }}} */
前兩個特徵都是在編譯階段,生成器函數編譯完,獲得的opcode爲DO_FCALL/DO_FCALL_BY_NAME
,解析opcode,獲得對應的處理函數(handler)爲ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER/ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER
,這兩個函數對於生成器處理基本是相同的,最終會調用zend_generator_create_zval()
函數:
代碼片斷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); // 保存當前的vm_stack,以便後面恢復 current_stack->top = EG(vm_stack_top); /* 先保存當前執行的execute_data,後面恢復 */ current_execute_data = EG(current_execute_data); execute_data = zend_create_generator_execute_data(call, op_array, return_value); // 建立新的execute_data EG(current_execute_data) = current_execute_data; // 恢復以前的execute_data object_init_ex(return_value, zend_ce_generator); // 實例化生成器對象,賦給return_value,因此生成器函數返回的是生成器對象。 /* 若是當前執行的是對象方法,則增長對象的引用計數 */ if (Z_OBJ(call->This)) { Z_ADDREF(call->This); } /* 把上面建立新的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; /* 賦值給生成器函數返回值,真正是zend_generator,爲了存儲,轉爲zval類型,後面訪問Generator類的時候會介紹 */ execute_data->return_value = (zval*)generator; memset(&generator->execute_fake, 0, sizeof(zend_execute_data)); Z_OBJ(generator->execute_fake.This) = (zend_object *) generator; }
經過上面的代碼片斷能夠知道:生成器調用時,函數的返回值返回了一個生成器對象,這就是上面提到的第三個特徵。另外會申請本身的VM棧(vm_stack)跟原來的VM棧分離開來,互不干擾,每次執行生成器函數代碼時只要修改executor_globals(EG)相應指針就能夠切換到生成器函數本身的VM棧,這樣就恢復到了生成器函數以前的狀態。一般,execute_data在VM棧上分配(由於它實際上不進行任何內存分配,因此很快)。對於生成器,這不是最理想的,由於每次執行被暫停或恢復時都必須來回複製(至關大)的結構。 這就是爲何對於生成器,使用單獨的VM棧分配執行上下文,從而容許僅經過替換指針來保存和恢復它。
《3.3生成器對象的建立》中提到yield是一個表達式,
編譯的時候最終會調用zend_compile_yield()
函數,在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結構體,操做數1(OP1)爲value ,操做數2(OP2)爲key*/ opline = zend_emit_op(result, ZEND_YIELD, value_node_ptr, key_node_ptr); // ... }
從上面代碼片斷能夠看出,yield對應的opcode是ZEND_YIELD
,因此對應的處理函數爲ZEND_YIELD_SPEC_{OP1}_{OP2}_HANDLER
,生成的處理函數不少,可是代碼基本都是同樣的,都是由Zend/zend_vm_def.h中的ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMP|VAR|CV|UNUSED)
生成的:
Zend/zend_vm_execute.h(全部處理函數的存放文件)都是經過執行zend_vm_gen.php根據Zend/zend_vm_def.h的定義生成的。下面咱們看一下這個定義函數:
代碼片斷 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) { // 若是操做數1類型不是IS_UNUSED,也就是有返回值(yield value這類型) if (UNEXPECTED(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { // 前面一些判斷,基本意思就是把值賦給generator->value,也就是生成值,這裏就不貼代碼了 } else { // 若是不是引用類型 // 根據不一樣的類型,把值賦給generator->value,也就是生成值,這裏也不貼代碼了 } } else { // 若是操做數1類型是IS_UNUSED,也就是沒有返回值(yield這類型),則生成值爲NULL ZVAL_NULL(&generator->value); } /* 這部分是對key部分的處理 */ if (OP2_TYPE != IS_UNUSED) { // 若是操做數2類型不是IS_UNUSED,也就是有返回自定義的key(yield key => value這類型) // 根據不一樣的類型,把值賦給generator->key,也就是生成自定義的鍵,這裏也不貼代碼了 /* 若是鍵的值類型爲整型(IS_LONG)且大於當前自增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,則把下一個自增的值賦給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; } /* 遞增到下個op,這樣下次繼續執行就能夠從下個op開始執行了 */ 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(); // 中斷執行 }
從上面代碼片斷能夠看出:yield首先生成鍵和值(本質就是修改zend_generator的key和value),生成完鍵值後保存狀態,而後中斷生成器函數的執行。
前面兩節介紹了Generator類和生成器對象的結構及建立,咱們知道生成器對象能夠經過foreach訪問,也能夠單獨調用生成器對象接口訪問。本節介紹這兩種方式訪問生成器對象的底層實現,兩種訪問方式都是圍繞zend_generator這個結構開展。
前面《2.4 Generator類》已經提到過Generator類實現了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 ) }
對應C代碼的函數以下:
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是內核定義的一個宏,方便閱讀和開發,這裏不作介紹,底層代碼都在Zend/zend_generators.c:767-864。
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
, 獲取的是當前execute_data.This這個zval(類型爲object)的object值(zval.value)的地址。可是這裏強行轉換是否是以爲很奇怪?
還記得代碼片斷3.3.6中提到:
object_init_ex(return_value, zend_ce_generator); // 實例化生成器對象,賦給return_value,因此生成器函數返回的是生成器對象。
初始化函數object_init_ex()
最終會調用_object_and_properties_init()
函數,在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()
函數,那麼會調用create_object()
函數。而zend_ce_generator是有定義有create_object()
函數,該函數爲zend_generator_create()
,參見《3.1 Generator類的註冊及其存儲結構》:
代碼片斷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; } /* }}} */
內存裏存儲的是zend_generator,後面強制轉換爲zend_object,由於返回值要是zval類型,因此這裏作了強制轉換。這就能解釋爲何能夠generator = (zend_generator *) Z_OBJ_P(getThis())
。
回到正題,ZEND_METHOD(Generator, rewind)
獲得zend_generator後,調用zend_generator_rewind()
:
代碼片斷3.5.4:
static void inline zend_generator_rewind(zend_generator *generator) { zend_generator_ensure_initialized(generator); // 保證generator已經初始化過了 /* 若是已經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遍歷,由於foreach也會調用rewind,這個後面再介紹。
ZEND_METHOD(Generator, valid)
,檢查當前位置是否有效,若是無效,foreach會中止遍歷。
代碼片斷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後,調用zend_generator_get_current()
函數,獲取當前須要運行的zend_generator
,而後判斷爲NULL
,以此已經更多的值生成了,這在《3.2 zend_generator結構體》中詳細說明過。
ZEND_METHOD(Generator, current)
獲取當前元素的值。
代碼片斷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,而後判斷生成器函數是否結束(generator->execute_data != NULL
)而且有值(Z_TYPE(root->value) != IS_UNDEF
),而後把值返回。
ZEND_METHOD(Generator, key)
獲取當前元素的鍵,也就是yield生成值時的key,沒有指定會使用自增的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
存儲的就是當前元素的鍵,這在《3.2 zend_generator結構體》中詳細說明過。
ZEND_METHOD(Generator, next)
向前移動到下一個元素,也就是執行到下一個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()
函數,這個函數比較重要:
代碼片斷3.5.9:
ZEND_API void zend_generator_resume(zend_generator *orig_generator) { zend_generator *generator = zend_generator_get_current(orig_generator); // 獲取要執行生成器 /* 若是生成器函數已經結束,則直接返回,不能繼續執行 */ if (UNEXPECTED(!generator->execute_data)) { return; } try_again: // 這個標籤是個yield from用的,解析完yield from表達式,須要生成(yield)一個值。 /* 若是有ZEND_GENERATOR_CURRENTLY_RUNNING標識,則表示已經運行,已經運行的不能再調用這方法繼續運行 */ 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有值,說明是非生成器類的委託對象產生(yield from)的 */ if (UNEXPECTED(!Z_ISUNDEF(generator->values))) { if (EXPECTED(zend_generator_get_next_delegated_value(generator) == SUCCESS)) { // 委託對象有值則直接返回 return; } /* yield from沒有更多值生成,則繼續運行生成器函數後面的代碼 */ } /* Drop the AT_FIRST_YIELD flag */ orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD; { /* 保存當前執行的execute_data上下文和VM棧,以便後面恢復,這在前面已經介紹過了 */ 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); /* 修改執行器的指針,指向要運行的生成器函數和其相應的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; // ... /* 執行生成器函數的代碼 */ generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; zend_execute_ex(generator->execute_data); // 執行,遇到yield中止繼續執行 generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING; /* 修改VM棧相關的指針,由於上面運行過程當中,VM棧不夠,會從新申請新的MV棧,因此須要修改相關指針 */ if (EXPECTED(generator->execute_data)) { generator->stack = EG(vm_stack); generator->stack->top = EG(vm_stack_top); } /* 恢復原來保存的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()方法時再講 */ 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沒有生成值時,要從新進入(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()
函數,表面意思就是繼續運行生成器函數。前面是一些判斷,而後保存當前上下文,執行生成器代碼,遇到yield返回,而後恢復上下文。
(未完成)
(未完成)
foreach訪問生成器對象,其實就是調用zend_ce_generator->get_iterator
,這在《3.1Generator類的註冊及其存儲結構》中介紹過,這是一個鉤子,生成器用的是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; //設置迭代器對象的相關處理函數 ZVAL_COPY(&iterator->data, object); // 把zend_generator賦給iterator的data,後面會用到 return iterator; } /* }}} */
zend_generator_get_iterator()
把迭代器對象的相關處理函數設置爲zend_generator_iterator_functions
,使得迭代生成器對象是使用相應的自定義函數,主要函數有:
代碼片斷3.5.11:
zend_generator_iterator_valid() // 判斷當前位置是否有效 zend_generator_iterator_get_data() // 獲取當前元素的值 zend_generator_iterator_get_key() // 獲取當前元素的鍵 zend_generator_iterator_move_forward() // 向前移動到下一個元素 zend_generator_iterator_rewind() // 指向第一個元素
函數細節就不一一介紹了,跟《3.5.1 使用生成器對象接口訪問》的相應函數差很少的。這裏咱們僅僅分析zend_generator_iterator_rewind()
函數,其餘的都相似:
代碼片斷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); }
由於在初始化的時候已經把zend_generator
賦給iterator->data
,詳見代碼片斷3.5.10,因此這裏能夠從iterator拿到zend_generator對象,其餘幾個函數亦是如此。zend_generator_rewind()
函數在ZEND_METHOD(Generator, rewind)已經介紹過了,這裏就很少說了。
從生成器語法咱們知道:return語句會終止生成器的執行,若是沒有顯式return,則默認會在結束return null。生成器裏面的return語句的opcode是ZEND_GENERATOR_RETURN,而return語句的opcode應該是ZEND_RETURN,這個處理是pass_two()
函數裏:
代碼片斷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; } // ... } // ... }
從上面代碼能夠看出,若是是生成器函數裏面的return則把opcode由ZEND_RETURN修改成ZEND_GENERATOR_RETURN,對應的處理函數定義爲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); // 獲取當前運行的生成器函數 // ... 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); // } } /* 關閉生成器,釋放資源(包括申請的VM棧) */ zend_generator_close(generator, 1); /* 執行器返回 */ ZEND_VM_RETURN(); }
前面是根據不一樣類型,把值賦給retval,後面調用zend_generator_close()
關閉生成器,釋放資源,咱們來看看這個函數:
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; // ... /* 生成器函數執行過程當中出現了致命錯誤,也會執行zend_generator_close(). 可是爲啥後面的語句不執行暫時還不清楚 */ if (UNEXPECTED(CG(unclean_shutdown))) { generator->execute_data = NULL; return; } zend_vm_stack_free_extra_args(generator->execute_data); // 釋放額外的參數,也就是參數列表以外的 /* 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. } }
生成器底層實現僅介紹了yield部分實現,包括yield生成值、生成器的訪問以及生成器的終止。底層實現仍是很好理解的,基本圍繞着zend_generator結構體進行。yield from部分較複雜,目前還沒有分析清楚,有興趣的同窗能夠分析一下。