协程,简单来讲就是新建立一个协助程序(co = coroutine.create(func)),而后须要手动去启动它(coroutine.resume(co)),在它最终退出以前,它有可能暂停屡次返回阶段性的结果(coroutine.yield(co)),每一次暂停以后都必须手动去恢复它(coroutine.resume(co))。数组
协程在lua源文件中对应lcorolib.c,数组co_funcs中定义了c暴露给lua的接口。从上面的描述看和c函数调用有点类似,只不过c函数只有一个出口,因此不可能返回屡次。题外话,为何c函数只有一个出口?我本身粗浅的理解是由于c函数的全部信息都放在栈上,而c语言没有提供原生的保存/恢复栈空间的支持,因此没有中途退出后还能生新进入这个概念。实际上,协程和系统级别的进程切换更像一点,都是保存堆栈,而后恢复。我想最大的不一样就是协程知道接下来的控制权在哪里,而进程不知道。根本上它们想实现的功能就不同吧。函数
好了,那协程实现的要点就是堆栈的保存与恢复了。固然,这里的堆栈不是进程自己的堆栈,而是lua的soft stack。从代码上来讲吧:ui
82 static int luaB_cocreate (lua_State *L) { 83 lua_State *NL; 84 luaL_checktype(L, 1, LUA_TFUNCTION); 85 NL = lua_newthread(L); 86 lua_pushvalue(L, 1); /* move function to top */ 87 lua_xmove(L, NL, 1); /* move function from L to NL */ 88 return 1; 89 }
其中NL就是新建立的协程的栈,之后全部的保存/恢复都是针对这个栈。lua_State这个结构体里对协程实现最重要的是CallInfo *ci,CallInfo的定义以下:this
66 /* 67 ** information about a call 68 */ 69 typedef struct CallInfo { 70 StkId func; /* function index in the stack */ 71 StkId top; /* top for this function */ 72 struct CallInfo *previous, *next; /* dynamic call link */ 73 short nresults; /* expected number of results from this function */ 74 lu_byte callstatus; 75 ptrdiff_t extra; 76 union { 77 struct { /* only for Lua functions */ 78 StkId base; /* base for this function */ 79 const Instruction *savedpc; 80 } l; 81 struct { /* only for C functions */ 82 int ctx; /* context info. in case of yields */ 83 lua_CFunction k; /* continuation in case of yields */ 84 ptrdiff_t old_errfunc; 85 lu_byte old_allowhook; 86 lu_byte status; 87 } c; 88 } u; 89 } CallInfo;
其中func指向当前调用的函数在栈上的位置,而savedpc就是保存的指令执行位置(先无视union里的c),根据这两个值就能恢复函数的执行点。然而在yield的时候真正负责保存函数位置的是extra(保存func与栈顶的相对位置),在resume时func会根据extra来恢复,有没有这个须要我是表示怀疑的,由于就算resume传递的参数致使栈realloc,使func失效,但在luaD_reallocstack内会调用correctstack将调用链上全部的func从新设置为正确的值,因此这里是否是多余的呢?lua
在lua 5.2中调用路径包含c函数的时候也可以进行yield,只不过不甚好看。因为c函数不能保存堆栈,因此lua的策略是直接放弃当前c函数的栈幀,而让调用者自己提供一个continuation,当resume时调用上面被无视的uion里的c.k。没用过,因此也不深刻考究了。spa