本文主要介绍: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部分较复杂,目前还没有分析清楚,有兴趣的同窗能够分析一下。