[轉]-Lua協程的實現

來源:互聯網
上載者:User

標籤:

協程是個很好的東西,它能做的事情與線程相似,區別在於:協程是使用者可控的,有API給使用者來暫停和繼續執行,而線程由作業系統核心控制;另 外,協程也更加輕量級。這樣,在遇到某些可能阻塞的操作時,可以使用暫停協程讓出CPU;而當條件滿足時,可以繼續執行這個協程。目前在網路伺服器領域, 使用Lua協程最好的範例就是ngx_lua了,我自己的項目qnode也是藉助Lua協程的概念:每一個qnode中的微進程底層對應一個Lua協程, 這樣底層的非同步作業可以在使用者使用同步的方式寫出來。Coool。

來看看Lua協程內部是如何?的。

本質上,每個Lua協程其實也是對應一個LuaState指標,所以其實它內部也是一個完整的Lua虛擬機器—有完整的Lua堆棧結構,函數調用棧等 等等等,絕大部分之前對Lua虛擬機器的分析都可以直接套用到Lua協程中。於是,由Lua虛擬機器管理著這些隸屬於它的協程,當需要暫停當前運行協程的時 候,就儲存它的運行環境,切換到別的協程繼續執行。很簡單的實現。

來看看相關的API。

1) lua_newthread
建立一個Lua協程,最終會調用的API是luaE_newthread,Lua協程在Lua中也是一個獨立的Lua類型資料,它的類型是LUA_TTHREAD,建立完畢之後會照例初始化Lua的棧等結構,有一點需要注意的是,調用preinit_state初始化Lua協程的時候,傳入的global表指標是來自於Lua虛擬機器,換句話說,任何在Lua協程修改的全域變數,也會影響到其他的Lua協程包括Lua虛擬機器本身。

2)載入一個Lua檔案並且執行
對於一般的Lua虛擬機器,大可以直接調用luaL_dofile即可,它其實是一個宏:

#define luaL_dofile(L, fn)         (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))

展開來也就是當調用luaL_loadfile函數完成對該Lua檔案的解析,並且沒有錯誤時,調用lua_pcall函數執行這個Lua指令碼。

但是對於Lua協程而言,卻不能這麼做,需要調用luaL_loadfile然後再調用lua_resume函數。所以兩者的區別在於 lua_pcall函數和lua_resume函數。來看看lua_resume函數的實現。這個函數做的幾件事情:首先查看當前Lua協程的狀態對不 對,然後修改計數器:

 L->baseCcalls = ++L->nCcalls;

其次調用status = luaD_rawrunprotected(L, resume, L->top – nargs);,可以看到這個保護Lua函數堆棧的調用luaD_rawrunprotected最終調用了函數resume:

static void resume (lua_State *L, void *ud) {  StkId firstArg = cast(StkId, ud);  CallInfo *ci = L->ci;  if (L->status == 0) {  /* start coroutine? */    lua_assert(ci == L->base_ci && firstArg > L->base);    if (luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA)      return;  }  else {  /* resuming from previous yield */    lua_assert(L->status == LUA_YIELD);    L->status = 0;    if (!f_isLua(ci)) {  /* `common‘ yield? */      /* finish interrupted execution of `OP_CALL‘ */      lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL ||                 GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL);      if (luaD_poscall(L, firstArg))  /* complete it... */        L->top = L->ci->top;  /* and correct top if not multiple results */    }    else  /* yielded inside a hook: just continue its execution */      L->base = L->ci->base;  }  luaV_execute(L, cast_int(L->ci - L->base_ci));}

這個函數將執行Lua代碼的流程劃分成了幾個階段,如果調用

luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA

那麼說明這次調用返回的結果小於0,可以跟進luaD_precall函數看看什麼情況下會出現這樣的情況:

    n = (*curr_func(L)->c.f)(L);  /* do the actual call */    lua_lock(L);    if (n < 0)  /* yielding? */      return PCRYIELD;    else {      luaD_poscall(L, L->top - n);      return PCRC;    }

繼續回到resume函數中,如果之前該Lua協程的狀態是YIELD,那麼說明之前被中斷了,則調用luaD_poscall完成這個函數的調用。
然後緊跟著調用luaV_execute繼續Lua虛擬機器的繼續執行。

可以看到,resume函數做的事情其實有那麼幾件:
1)如果調用C函數時被YIELD了,則直接返回
2)如果之前被YIELD了,則調用luaD_poscall完成這個函數的執行,接著調用luaV_execute繼續Lua虛擬機器的執行。
因此,這個函數對於函數執行中可能出現的YIELD,有充分的準備和判斷,因此它不像一般的pcall那樣,一股腦的往下執行,而是會在出現YIELD的時候儲存現場返回,在繼續執行的時候恢複現場。
3)同時,由於resume函數是由luaD_rawrunprotected進行保護調用的,即使執行出錯,也不會造成整個程式的退出。

這就是Lua協程中,比一般的Lua操作過程做的更多的地方。

最後給出一個Lua協程的例子:
co.lua

print("before")test("123")print("after resume")

co.c

 #include     #include "lua.h"    #include "lualib.h"    #include "lauxlib.h"    static int panic(lua_State *state) {      printf("PANIC: unprotected error in call to Lua API (%s)\n",              lua_tostring(state, -1));      return 0;    }    static int test(lua_State *state) {      printf("in test\n");      printf("yielding\n");      return lua_yield(state, 0);    }    int main(int argc, char *argv[]) {      char *name = NULL;      name = "co.lua";      lua_State*  L1 = NULL;      L1 = lua_open();      lua_atpanic(L1, panic);      luaL_openlibs( L1 );      lua_register(L1, "test", test);      lua_State*  L = lua_newthread(L1);      luaL_loadfile(L, name);      lua_resume(L, 0);      printf("sleeping\n");      sleep(1);      lua_resume(L, 0);      printf("after resume test\n");      return 0;    }

[轉]-Lua協程的實現

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.