欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++與Lua協(xié)程交互的示例詳解

 更新時間:2024年02月10日 09:10:58   作者:江澎涌  
Lua 語言不支持真正的多線程,即不支持共享內(nèi)存的搶占式線程,在執(zhí)行協(xié)程體的 Lua 腳本時,Lua 同樣可以調(diào)用 C++ 的函數(shù),本文給大家介紹了C++ 與 Lua 的協(xié)程交互,需要的朋友可以參考下

零、前言

Lua 語言不支持真正的多線程,即不支持共享內(nèi)存的搶占式線程。

這樣的模式能減少一些多線程的問題。多線程的問題源于線程搶占和共享內(nèi)存,而如果非搶占式線程或者不使用共享內(nèi)存則能避免多線程問題,Lua 同時支持這兩種方案。從之前分享的《Lua 協(xié)程》文章中知道:

  • Lua 語言的線程是協(xié)作式的,即協(xié)程,可以避免因不可預(yù)知的線程切換帶來的問題。
  • Lua 狀態(tài)間內(nèi)存不共享,所以各個狀態(tài)相互獨(dú)立運(yùn)行,可以并行操作。

一、多線程

從 C-API 的角度,可以把線程當(dāng)作一個棧,每個棧保存著一個線程中掛起的函數(shù)調(diào)用信息,以及每個函數(shù)調(diào)用的參數(shù)和局部變量。也就是說,一個棧包括了一個線程得以繼續(xù)運(yùn)行所需的所有信息。 因此,要達(dá)到多線程就需要多個棧。

每當(dāng)創(chuàng)建一個 lua_State 時,Lua 就會自動用這個 lua_State 創(chuàng)建一個主線程。這個主線程永遠(yuǎn)不會被垃圾回收,只有當(dāng)調(diào)用 lua_close 關(guān)閉時才會釋放。

可以通過 C-API lua_newthread 在已有的 lua_State 中創(chuàng)建新的線程。

二、lua_newthread

lua_State *(lua_newthread) (lua_State *L);

描述:

這個函數(shù)會將新線程 thread 類型的值壓入棧中,并返回一個表示該線程的 lua_State 類型的指針。

新線程和舊線程的異同點(diǎn):

可以使用以下代碼創(chuàng)建了一個新的線程,并且得到一個新的 lua_State 指針類型的 L1 值。

lua_State *L1 = lua_newthread(L);
  • 新線程 L1 與主線程 L 共享全局變量和注冊表,但是它們具有獨(dú)立的??臻g。新線程 L1 從空棧開始,主線程 L 則在其棧頂會引用 L1 這個新線程。
  • 除了主線程 L 需要使用 lua_close 進(jìn)行關(guān)閉,新線程 L1 和其他的 Lua 對象一樣都是垃圾回收的對象。所以永遠(yuǎn)不要使用未被正確錨定在 lua_State 中的線程(主線程是內(nèi)部錨定,因此不用擔(dān)心會被回收),因為所有對 Lua API 的調(diào)用都有可能回收未錨定的線程,即使是正在使用這個線程進(jìn)行函數(shù)調(diào)用。 要避免這種情況,應(yīng)該在 “已錨定的線程的棧”、“注冊表”、“Lua 變量” 中保留對使用中線程的引用,防止被垃圾回收機(jī)制回收。
  • 擁有的新線程 L1 可以像主線程 L 一樣使用,進(jìn)行壓棧調(diào)用函數(shù)等操作。

舉個例子:

  • 創(chuàng)建一個新的線程。
  • 分別打印各自棧的內(nèi)容。
lua_State *L = luaL_newstate();
luaL_openlibs(L);

// 用 L 創(chuàng)建一個新的線程
// L 和 L1 各自有一個棧
// L 的棧頂是 L1 的 thread
// L1 的棧是空
lua_State *L1 = lua_newthread(L);

printf("主線程 L 棧深度:%d\n", lua_gettop(L));
printf("主線程 L 棧內(nèi)容:------------\n");
stackDump(L);

printf("新線程 L1 棧深度:%d\n", lua_gettop(L1));
printf("新線程 L1 棧內(nèi)容:------------\n");
stackDump(L1);

lua_close(L);

輸出如下:

主線程 L 棧深度:1
------------ 主線程 L 棧內(nèi)容:------------
棧頂
^ typename: thread, value: thread    
棧底

新線程 L1 棧深度:0
------------ 新線程 L1 棧內(nèi)容:------------
棧頂
棧底

三、新線程的作用

如果創(chuàng)建一個新的線程,只是用來運(yùn)行簡單的函數(shù),這種場景其實只需要在主線程中執(zhí)行即可。

創(chuàng)建新線程的主要目的是運(yùn)行協(xié)程。 可以在新線程中掛起協(xié)程,然后繼續(xù)運(yùn)行其他線程的 Lua 代碼,在需要的節(jié)點(diǎn)恢復(fù)協(xié)程,使新線程繼續(xù)執(zhí)行掛起點(diǎn)之后的邏輯代碼,而運(yùn)行協(xié)程需要用到 lua_resume C-API 。

四、lua_resume

LUA_API int  (lua_resume)     (lua_State *L, lua_State *from, int narg,
                               int *nres);

描述:

使用 lua_resume 和使用 lua_pcall 調(diào)用函數(shù)很相似,壓入?yún)f(xié)程的參數(shù),然后將待調(diào)用函數(shù)(協(xié)程體)壓入棧,并以參數(shù)的數(shù)量作為參數(shù) narg 調(diào)用 lua_resume

lua_resumelua_pcall 的不同點(diǎn):

  • lua_resume 中沒有表示期望結(jié)果數(shù)量的參數(shù),總是返回被調(diào)用函數(shù)的所有結(jié)果。
  • 沒有錯誤處理函數(shù)的參數(shù),發(fā)生錯誤時不會進(jìn)行棧展開,可以在錯誤發(fā)生后檢查棧的情況。
  • 如果正在運(yùn)行的函數(shù)被掛起,lua_resume 就會返回代碼 LUA_YIELD ,并將線程置于一個后續(xù)可以恢復(fù)執(zhí)行的狀態(tài)中。

參數(shù):

  • 參數(shù) L:Lua State 的指針。
  • 參數(shù) from:正在執(zhí)行調(diào)用的線程,或為 NULL 。
  • 參數(shù) narg:入?yún)?shù)數(shù)量。
  • 參數(shù) nres:協(xié)程體返回的值數(shù)量。

返回值:

返回當(dāng)前協(xié)程體處于哪個狀態(tài):

  • 如果被掛起,則返回 LUA_YIELD 。
  • 如果已經(jīng)執(zhí)行完成,則返回 LUA_OK
  • 如果 Lua 腳本中拋出異常,則會返回 LUA_ERRxxx 類型的錯誤。

值傳遞:

lua_Stack 的棧是相互獨(dú)立,所以此時如果需要將協(xié)程產(chǎn)出的數(shù)據(jù)傳遞到另一個 lua_State 中。

  • 可以將需要的數(shù)據(jù)通過 C-API 獲取至 C 中,處理之后,再壓入另一個 lua_State 的棧中。
  • 可以使用 lua_xmove 將數(shù)據(jù)傳遞至另一個 lua_State 中。

Lua 調(diào)用 C++ 時,C++ 函數(shù)掛起協(xié)程體:

在執(zhí)行協(xié)程體的 Lua 腳本時,Lua 同樣可以調(diào)用 C++ 的函數(shù)。C++ 函數(shù)體內(nèi)也可以進(jìn)行協(xié)程掛起,通過 lua_yieldk 便可以進(jìn)行達(dá)到協(xié)程掛起。

五、lua_yieldk

LUA_API int  (lua_yieldk)     (lua_State *L, int nresults, lua_KContext ctx,
                               lua_KFunction k);

描述:

將正在運(yùn)行的協(xié)程體立即掛起。

當(dāng)協(xié)程恢復(fù)運(yùn)行時,控制權(quán)會直接交給延續(xù)函數(shù) k 。當(dāng)協(xié)程交出控制后,調(diào)用 lua_yield 的函數(shù)后續(xù)語句就不會被執(zhí)行了。

參數(shù):

  • 參數(shù) L:Lua State 的指針。
  • 參數(shù) nresults:將要返回給對應(yīng)的 lua_resume 的棧中值的個數(shù)。
  • 參數(shù) ctx:傳遞給延續(xù)的上下文信息。
  • 參數(shù) k:延續(xù)函數(shù)。

值得注意:

如果 C++ 函數(shù)交出控制權(quán)之后,無需做后續(xù)處理,即掛起再恢復(fù)后,無需執(zhí)行后續(xù)操作,可以不設(shè)置參數(shù) k (設(shè)置為 NULL)。具體代碼如下所示:

#define lua_yield(L,n)		lua_yieldk(L, (n), 0, NULL)

六、C++ 與 Lua 協(xié)程交互的例子

  • 創(chuàng)建一個新線程 lua_State ,在這個新線程中執(zhí)行協(xié)程體。
  • 協(xié)程體內(nèi),首先會在 Lua 腳本中掛起,并返回數(shù)據(jù)到宿主中。
  • 恢復(fù)協(xié)程體后,Lua 從掛起點(diǎn)繼續(xù)運(yùn)行,調(diào)用 C++ 函數(shù),C++ 函數(shù)內(nèi)掛起協(xié)程體,并返回數(shù)據(jù)到宿主。
  • 再次恢復(fù)協(xié)程后,運(yùn)行 C++ 函數(shù)掛起點(diǎn)的延續(xù)函數(shù),運(yùn)行完延續(xù)函數(shù),回至 Lua 調(diào)用 C++ 調(diào)用點(diǎn)繼續(xù)執(zhí)行至結(jié)束,返回數(shù)據(jù)到宿主。

第一步,編寫 C++ 函數(shù),暴露給 Lua 調(diào)用。

// 延續(xù)函數(shù)
int cfooK(lua_State *L, int status, lua_KContext ctx) {
    printf("恢復(fù)協(xié)程體后,調(diào)用 C++ 延續(xù)函數(shù)\n");
    return 1;
}

// 暴露給 lua 調(diào)用的 C++ 函數(shù)
int primCFunction(lua_State *L) {
    lua_pushstring(L, "調(diào)用 C++ 函數(shù),會進(jìn)行掛起");

    // 設(shè)置了 cfooK 作為延續(xù)函數(shù),恢復(fù)協(xié)程之后,會進(jìn)入 cfook 這一延續(xù)函數(shù)
    lua_yieldk(L, 1, 0, &cfooK);

    // 這兩種的使用結(jié)果是一樣的,都不舍之延續(xù)函數(shù)
//    lua_yieldk(L, 1, 0, nullptr);
//    lua_yield(L, 1);

    // 不會被執(zhí)行,恢復(fù)之后會運(yùn)行延續(xù)函數(shù),不會執(zhí)行后續(xù)的語句
    printf("掛起之后的輸(不會被輸出)");

    return 1;
}

第二步,編寫 Lua 腳本文件。

function foo(x)
    -- 協(xié)程掛起,并返回兩個數(shù)據(jù)
    coroutine.yield(10, x)
end

function foo1(x)
    foo(x + 1)

    primCFunction()

    return 3
end

第三步,最后編寫運(yùn)行代碼。

void useThread() {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    lua_State *L1 = lua_newthread(L);

    lua_pushcfunction(L1, primCFunction);
    lua_setglobal(L1, "primCFunction");

    std::string fname = PROJECT_PATH + "/12、線程和狀態(tài)/thread/coroutine.lua";
    if (luaL_loadfile(L1, fname.c_str()) || lua_pcall(L1, 0, 0, 0)) {
        printf("can't run config. file: %s", lua_tostring(L1, -1));
    }

    printf("LUA_YIELD=%d\n", LUA_YIELD);
    printf("LUA_OK=%d\n", LUA_OK);

    // 返回值個數(shù)
    int result = 0;
    // 獲取 L1 中的 lua 文件的函數(shù) foo1
    lua_getglobal(L1, "foo1");
    // 壓入 integer
    lua_pushinteger(L1, 20);

    printf("第一次調(diào)用,Lua 腳本中掛起:\n");
    auto state = lua_resume(L1, L, 1, &result);
    printf("協(xié)程狀態(tài): %s\n", getResumeState(state).c_str());
    printf("L1 棧深度: %d\n", lua_gettop(L1));
    printf("返回值個數(shù): %d\n", result);
    printf("------------ L1 棧內(nèi)容:------------\n");
    stackDump(L1);

    printf("第二次調(diào)用,Lua 調(diào)用 C++ ,C++ 中掛起:\n");
    state = lua_resume(L1, L, 0, &result);
    printf("協(xié)程狀態(tài): %s\n", getResumeState(state).c_str());
    printf("L1 棧深度: %d\n", lua_gettop(L1));
    printf("返回值個數(shù): %d\n", result);
    printf("------------ L1 棧內(nèi)容:------------\n");
    stackDump(L1);

    printf("第三次調(diào)用,運(yùn)行 C++ 延續(xù)函數(shù),協(xié)程體結(jié)束::\n");
    state = lua_resume(L1, L, 0, &result);
    printf("協(xié)程狀態(tài): %s\n", getResumeState(state).c_str());
    printf("L1 棧深度: %d\n", lua_gettop(L1));
    printf("返回值個數(shù): %d\n", result);
    printf("------------ L1 棧內(nèi)容:------------\n");
    stackDump(L1);

    lua_close(L);
}

最后運(yùn)行的結(jié)果如下,這里就不再一個個步驟拆解了,代碼有詳細(xì)的注釋,結(jié)合運(yùn)行的結(jié)果便可分析出流轉(zhuǎn)的過程了。

LUA_YIELD=1
LUA_OK=0
第一次調(diào)用,Lua 腳本中掛起:
協(xié)程狀態(tài): LUA_YIELD
L1 棧深度: 2
返回值個數(shù): 2
------------ L1 棧內(nèi)容:------------
棧頂
^ typename: number, value(integer): 21    
^ typename: number, value(integer): 10    
棧底

第二次調(diào)用,Lua 調(diào)用 C++ ,C++ 中掛起:
協(xié)程狀態(tài): LUA_YIELD
L1 棧深度: 1
返回值個數(shù): 1
------------ L1 棧內(nèi)容:------------
棧頂
^ typename: string, value: '調(diào)用 C++ 函數(shù),會進(jìn)行掛起'    
棧底

第三次調(diào)用,運(yùn)行 C++ 延續(xù)函數(shù),協(xié)程體結(jié)束::
協(xié)程狀態(tài): LUA_OK
L1 棧深度: 1
返回值個數(shù): 1
------------ L1 棧內(nèi)容:------------
棧頂
^ typename: number, value(integer): 3    
棧底

七、lua_xmove

LUA_API void  (lua_xmove) (lua_State *from, lua_State *to, int n);

描述:

交換同一個狀態(tài)機(jī)下不同線程中的值,會從 from 的棧上彈出 n 個值, 然后把它們壓入 to 的棧上。

舉個例子:

  • 創(chuàng)建一個 lua_State "L",然后從這一 lua_State 創(chuàng)建一個新的線程 lua_State "L1"。
  • 通過 L1 執(zhí)行 Lua 腳本,得到兩個數(shù)據(jù)。
  • 將 L1 的數(shù)據(jù)移動到 L 中,打印結(jié)果。

第一步,編寫 lua 腳本。

function foo(x)
    return "江澎涌", x * x
end

第二步,編寫主函數(shù)。

void copyStackElement() {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    lua_State *L1 = lua_newthread(L);

    // 新線程中執(zhí)行 Lua 腳本
    std::string fname = PROJECT_PATH + "/12、線程和狀態(tài)/thread/copy_stack.lua";
    if (luaL_loadfile(L1, fname.c_str()) || lua_pcall(L1, 0, 0, 0)) {
        printf("can't run config. file: %s", lua_tostring(L1, -1));
    }

    // 獲取 L1 中的 lua 文件的函數(shù) foo
    lua_getglobal(L1, "foo");
    // 壓入 integer
    lua_pushinteger(L1, 5);
    // 調(diào)用函數(shù) foo(5) -> "江澎涌", 25
    lua_call(L1, 1, 2);

    printf("------------ 主線程 L 棧內(nèi)容(xmove 前):--------------\n");
    stackDump(L);

    printf("------------ 新線程 L1 棧內(nèi)容(xmove 前):--------------\n");
    stackDump(L1);
    // 從 L1 中拷貝 1 個元素到 L 中(會將 L1 元素彈出)
    lua_xmove(L1, L, 2);

    printf("------------ 主線程 L 棧內(nèi)容(xmove 后):--------------\n");
    stackDump(L);

    printf("------------ 新線程 L1 棧內(nèi)容(xmove 后):--------------\n");
    stackDump(L1);
}

八、寫在最后

Lua 項目地址:Github傳送門

以上就是C++與Lua協(xié)程交互的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于C++與Lua協(xié)程交互的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語言詳細(xì)講解二分查找用法

    C語言詳細(xì)講解二分查找用法

    二分查找法,又叫做折半查找法,它是一種效率較高的查找方法。但是,折半查找要求線性表必須采用順序存儲結(jié)構(gòu),而且表中元素按關(guān)鍵字有序排列
    2022-04-04
  • C語言實現(xiàn)2048小游戲

    C語言實現(xiàn)2048小游戲

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)2048小游戲,注釋清晰,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • VC基于ADO技術(shù)訪問數(shù)據(jù)庫的方法

    VC基于ADO技術(shù)訪問數(shù)據(jù)庫的方法

    這篇文章主要介紹了VC基于ADO技術(shù)訪問數(shù)據(jù)庫的方法,較為詳細(xì)的分析了VC使用ADO操作數(shù)據(jù)庫的相關(guān)實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-10-10
  • C/C++中的內(nèi)存管理小結(jié)

    C/C++中的內(nèi)存管理小結(jié)

    這篇文章主要介紹了C/C++中的內(nèi)存管理小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • c++歸并排序詳解

    c++歸并排序詳解

    歸并排序遵循分治法的思想:將原問題分解為幾個規(guī)模較小但類似于原問題的子問題,遞歸地求解這些子問題,然后再合并這些子問題的解來建立原問題的解。分治模式在每層遞歸時都有三個步驟:分解、解決、合并。歸并排序完全遵循該模式。
    2017-05-05
  • 關(guān)于在MFC中將窗口最小化到托盤實現(xiàn)原理及操作步驟

    關(guān)于在MFC中將窗口最小化到托盤實現(xiàn)原理及操作步驟

    最小化的原理:首先要將窗口隱藏,然后在右下角繪制圖標(biāo);恢復(fù)的原理:將窗口顯示,再將托盤中的圖片刪除,接下來介紹實現(xiàn)方法,感興趣的朋友可以了解下啊,希望本文對你有所幫助
    2013-01-01
  • C++中 Sort函數(shù)詳細(xì)解析

    C++中 Sort函數(shù)詳細(xì)解析

    這篇文章主要介紹了C++中 Sort函數(shù)詳細(xì)解析,sort函數(shù)是algorithm庫下的一個函數(shù),sort函數(shù)是不穩(wěn)定的,即大小相同的元素在排序后相對順序可能發(fā)生改變
    2022-08-08
  • C++實現(xiàn)KFC點(diǎn)餐系統(tǒng)

    C++實現(xiàn)KFC點(diǎn)餐系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C++實現(xiàn)KFC點(diǎn)餐系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • C語言實現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用鏈表

    C語言實現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用鏈表

    這篇文章主要為大家詳細(xì)介紹了c語言實現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用鏈表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • c語言排序之歸并排序(遞歸和非遞歸)

    c語言排序之歸并排序(遞歸和非遞歸)

    這篇文章主要介紹了?c語言排序之歸并排序(遞歸和非遞歸),歸并就是把兩個或多個序列合并,本文主要介紹二路歸并,下文相關(guān)資料需要的小伙伴可以參考一下
    2022-04-04

最新評論