lua協程實現

来源:https://www.cnblogs.com/bytemode/archive/2018/12/15/10124202.html
-Advertisement-
Play Games

協程是個很好的東西,它能做的事情與線程相似,區別在於:協程是使用者可控的,有API給使用者來暫停和繼續執行,而線程由操作系統內核控制;另外,協程也更加輕量級。這樣,在遇到某些可能阻塞的操作時,可以使用暫停協程讓出CPU;而當條件滿足時,可以繼續執行這個協程。目前在網路伺服器領域,使用Lua協程最好的 ...


協程是個很好的東西,它能做的事情與線程相似,區別在於:協程是使用者可控的,有API給使用者來暫停和繼續執行,而線程由操作系統內核控制;另外,協程也更加輕量級。這樣,在遇到某些可能阻塞的操作時,可以使用暫停協程讓出CPU;而當條件滿足時,可以繼續執行這個協程。目前在網路伺服器領域,使用Lua協程最好的範例就是ngx_lua了

來看看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虛擬機本身。

  1. 載入一個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;
    }

你可以使用coroutine.create來創建協程,協程有三種狀態:掛起,運行,停止。創建後是掛起狀態,即不自動運行。status函數可以查看當前狀態。協程真正強大的地方在於他可以通過yield函數將一段正在運行的代碼掛起。

lua的resume-yield可以互相交換數據

co = coroutine.create(function (a, b)
     coroutine.yield(a+b, a-b)
end)
print(coroutine.resume(co, 3, 8))

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Inno Setup 系列之安裝、卸載前檢測進程運行情況並關閉相應進程 ...
  • 從頭開始學習OI之Tarjan. 今天重新學習了Tarjan演算法..來這裡寫一下學習筆記...但是我太菜了..還是講不好怎麼辦... ...
  • 給定一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出所有滿足條件且不重覆的三元組。 註意:答案中不可以包含重覆的三元組。 我們先對數組排序,得到如下圖的結果 我們要計算a+b+c=0,先對數組迴圈得到a,然後b就是a的索 ...
  • 題目內容: 這一周的編程題是需要你在課程所給的時鐘程式的基礎上修改而成。但是我們並不直接給你時鐘程式的代碼,請根據視頻自己輸入時鐘程式的Display和Clock類的代碼,然後來做這個題目。 我們需要給時鐘程式加上一個表示秒的Display,然後為Clock增加以下public的成員函數: publ ...
  • 一 在下麵兩種情況下使用靜態方法: 1.當一個方法不需要訪問對象轉態,其所需的參數讀書通過顯示參數提供的(例如 Math.pow). 2.當一個方法只需要訪問類靜態域(enployee.getNextld). 二 方法參數的使用情況 一個方法不能修改一個基本數據類型的參數(即數值型和布爾型). 一個 ...
  • 等值、大小比較 在python中, 只要兩個對象的類型相同,且它們是內置類型(字典除外),那麼這兩個對象就能進行比較 。關鍵詞:內置類型、同類型。所以,兩個對象如果類型不同,就沒法比較,比如數值類型的數值不能和字元串類型的數值或字母比較。 對於python中的等值、不等值、大小比較的規則為何如此,以 ...
  • 布爾類型 python中True表示真,False表示假,它們是布爾類型: 在python中,bool的True和False是數值1和0的字元串表示格式,實際上 bool類型是int類型的一個子類 。 因為True/False是數值1和0的另一種表示方式,它們可以直接參与數值運算。 True/Fal ...
  • 在現代Linux桌面環境上我們時常可以看到類似的消息框: 這些消息框常用在如下場景: 即時聊天軟體的新消息 鬧鐘定時提示 電池電量提示 郵件消息 長耗時操作的完成提示 在freedesktop.org的規範中這種消息框被稱為 ,中文名我們形象得稱其為“氣泡框”。通過調用D BUS服務 提供的介面即可 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...