線程(thread)作爲Lua中一種基本的數據類型,它代表獨立的執行線程(independent threads of execution),線程類型是實現協程(coroutines)的基礎,注意這裏的線程類型不要與操作系統線程混淆,Lua的線程類型是Lua虛擬機實現一種數據類型。
從Lua腳本來看,一個協程就是一個線程類型,比如:
- local co = coroutine.create(function() print("hi") end)
- print(co) --output: thread: 0038BEE0
從實現角度來看,一個線程類型數據就是一個Lua與C交互的棧,每個棧包含函數調用鏈和數據棧,還有獨立的調試鉤子和錯誤信息。關於Lua與C交互的棧的可以參考之前寫的一篇文章。線程類型數據與table數據類型類似,它也是需要GC來管理的。
當調用Lua C API中的大多數函數時,這些函數都是在特定的棧(或線程)上,因此在很多API中,第一個參數是lua_State*(線程類型對應的數據結構就是lua_State,或者說交互的棧就是用結構體lua_State來實現的),這個參數不僅表示了一個Lua狀態,還表示一個記錄在該狀態中的線程。注意這裏的狀態,對一個虛擬機來說只有一個,而一個虛擬機可以包括多個線程(或者說交互棧)。這個狀態也就是虛擬機的全局狀態,我們可以通過調用函數luaL_newstate()來創建一個虛擬機的狀態,該函數聲明如下:
- lua_State *lua_newstate (lua_Alloc f, void *ud);
- lua_State *lua_newthread (lua_State *L);
總之,一個Lua虛擬機只有一個全局的狀態,但可以包含多個執行環境(或者說多個線程、交互棧,從腳本角度來說,也可以說是多個協程),也就是說多個執行環境共享一個全局狀態。如下圖所示:
下面將通過Lua 5.2.1的源碼來看全局狀態的數據結構global_State和腳本執行的相關的上下文環境結構lua_State,以及函數lua_newstate和lua_newthread的實現。
2、源碼實現
首先來分析全局狀態的結構體global_State的代碼(lstate.h):
- 109 /*
- 110 ** `global state', shared by all threads of this state
- 111 */
- 112 typedef struct global_State {
- 113 lua_Alloc frealloc; /* function to reallocate memory */
- 114 void *ud; /* auxiliary data to `frealloc' */
- 115 lu_mem totalbytes; /* number of bytes currently allocated - GCdebt */
- 116 l_mem GCdebt; /* bytes allocated not yet compensated by the collector */
- 117 lu_mem GCmemtrav; /* memory traversed by the GC */
- 118 lu_mem GCestimate; /* an estimate of the non-garbage memory in use */
- 119 stringtable strt; /* hash table for strings */
- 120 TValue l_registry;
- 121 unsigned int seed; /* randomized seed for hashes */
- 122 lu_byte currentwhite;
- 123 lu_byte gcstate; /* state of garbage collector */
- 124 lu_byte gckind; /* kind of GC running */
- 125 lu_byte gcrunning; /* true if GC is running */
- 126 int sweepstrgc; /* position of sweep in `strt' */
- 127 GCObject *allgc; /* list of all collectable objects */
- 128 GCObject *finobj; /* list of collectable objects with finalizers */
- 129 GCObject **sweepgc; /* current position of sweep in list 'allgc' */
- 130 GCObject **sweepfin; /* current position of sweep in list 'finobj' */
- 131 GCObject *gray; /* list of gray objects */
- 132 GCObject *grayagain; /* list of objects to be traversed atomically */
- 133 GCObject *weak; /* list of tables with weak values */
- 134 GCObject *ephemeron; /* list of ephemeron tables (weak keys) */
- 135 GCObject *allweak; /* list of all-weak tables */
- 136 GCObject *tobefnz; /* list of userdata to be GC */
- 137 UpVal uvhead; /* head of double-linked list of all open upvalues */
- 138 Mbuffer buff; /* temporary buffer for string concatenation */
- 139 int gcpause; /* size of pause between successive GCs */
- 140 int gcmajorinc; /* how much to wait for a major GC (only in gen. mode) */
- 141 int gcstepmul; /* GC `granularity' */
- 142 lua_CFunction panic; /* to be called in unprotected errors */
- 143 struct lua_State *mainthread;
- 144 const lua_Number *version; /* pointer to version number */
- 145 TString *memerrmsg; /* memory-error message */
- 146 TString *tmname[TM_N]; /* array with tag-method names */
- 147 struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */
- 148 } global_State;
lua_Alloc frealloc:虛擬機內存分配策略,可以在調用lua_newstate時指定參數,修改該策略,或者調用luaL_newstate函數使用默認的內存分配策略。也可以通過函數 lua_setallocf:來設置內存分配策略。
stringtable strt:全局的字符串哈希表,即保存那些短字符串,使得整個虛擬機中短字符串只有一份實例。
TValue l_registry:保存全局的註冊表,註冊表就是一個全局的table(即整個虛擬機中只有一個註冊表),它只能被C代碼訪問,通常,它用來保存那些需要在幾個模塊中共享的數據。比如通過luaL_newmetatable創建的元表就是放在全局的註冊表中。
lua_CFunction panic:但出現無包含錯誤(unprotected errors)時,會調用這個函數。這個函數可以通過lua_atpanic來修改。
UpVal uvhead:指向保存所有open upvalues雙向鏈表的頭部。
struct Table *mt[LUA_NUMTAGS]:保存基本類型的元表,注意table和userdata都有自己的元表。
struct lua_State *mainthread:指向主lua_State,或者說是主線程、主執行棧。Lua虛擬機在調用函數lua_newstate初始化全局狀態global_State時也會創建一個主線程,當然根據需要也可以調用lua_newthread來創建新的線程,但是整個虛擬機,只有一個全局的狀態global_State。
全局狀態結構體中其他成員基本都是與內存管理和GC相關的。
下面來看線程對應的數據結構lua_State的實現,代碼如下(lstate.h):
- 151 /*
- 152 ** `per thread' state
- 153 */
- 154 struct lua_State {
- 155 CommonHeader;
- 156 lu_byte status;
- 157 StkId top; /* first free slot in the stack */
- 158 global_State *l_G;
- 159 CallInfo *ci; /* call info for current function */
- 160 const Instruction *oldpc; /* last pc traced */
- 161 StkId stack_last; /* last free slot in the stack */
- 162 StkId stack; /* stack base */
- 163 int stacksize;
- 164 unsigned short nny; /* number of non-yieldable calls in stack */
- 165 unsigned short nCcalls; /* number of nested C calls */
- 166 lu_byte hookmask;
- 167 lu_byte allowhook;
- 168 int basehookcount;
- 169 int hookcount;
- 170 lua_Hook hook;
- 171 GCObject *openupval; /* list of open upvalues in this stack */
- 172 GCObject *gclist;
- 173 struct lua_longjmp *errorJmp; /* current error recover point */
- 174 ptrdiff_t errfunc; /* current error handling function (stack index) */
- 175 CallInfo base_ci; /* CallInfo for first level (C calling Lua) */
- 176 };
可以看到,lua_State結構跟其他可回收的數據類型類型,結構體帶用CommonHeader的頭,它也GC回收的對象之一。它主要包括以下成員信息:
lu_byte status:線程腳本的狀態,線程可能狀態如下(lua.h):
- 44 /* thread status */
- 45 #define LUA_OK 0
- 46 #define LUA_YIELD 1
- 47 #define LUA_ERRRUN 2
- 48 #define LUA_ERRSYNTAX 3
- 49 #define LUA_ERRMEM 4
- 50 #define LUA_ERRGCMM 5
- 51 #define LUA_ERRERR 6
其他成員主要是數據棧和函數調用棧相關的,這也是lua_State結構中主要信息。還有成員ptrdiff_t errfunc是錯誤處理函數相關,也就是每個調用棧都有獨立的錯誤處理函數,以及調試相關的lua_Hook hook成員等。
3、總結
在調用lua_newstate 初始化Lua虛擬機時,會創建一個全局狀態和一個線程(或稱爲調用棧),這個全局狀態在整個虛擬機中是唯一的,供其他線程共享。一個Lua虛擬機中可以包括多個線程,這些線程共享一個全局狀態,線程之間也可以調用lua_xmove函數來交換數據。
參考資料
http://www.cnblogs.com/ringofthec/archive/2010/11/09/lua_State.html lua API 小記3(lua虛擬機初始化)
http://blog.aliyun.com/795 Lua數據結構 — lua_State(六)
Lua 5.2.1源碼