協程,簡單來講就是新建立一個協助程序(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