协程,简单来说就是新创建一个协助程序(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。从代码上来说吧:

 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的定义如下:

 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 5.2中调用路径包含c函数的时候也能够进行yield,只不过不甚好看。由于c函数不能保存堆栈,所以lua的策略是直接放弃当前c函数的栈幀,而让调用者本身提供一个continuation,当resume时调用上面被无视的uion里的c.k。没用过,所以也不深入考究了。

 posted on 2014-01-08 23:48  万事屋madao  阅读(5135)  评论(0编辑  收藏  举报