亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

零、前言

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

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

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

一、多線程

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

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

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

二、lua_newthread

lua_State *(lua_newthread) (lua_State *L);

描述:

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

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

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

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

舉個(gè)例子:

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

// 用 L 創(chuàng)建一個(gè)新的線程
// L 和 L1 各自有一個(gè)棧
// 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)建一個(gè)新的線程,只是用來(lái)運(yùn)行簡(jiǎn)單的函數(shù),這種場(chǎng)景其實(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 中沒(méi)有表示期望結(jié)果數(shù)量的參數(shù),總是返回被調(diào)用函數(shù)的所有結(jié)果。
  • 沒(méi)有錯(cuò)誤處理函數(shù)的參數(shù),發(fā)生錯(cuò)誤時(shí)不會(huì)進(jìn)行棧展開(kāi),可以在錯(cuò)誤發(fā)生后檢查棧的情況。
  • 如果正在運(yùn)行的函數(shù)被掛起,lua_resume 就會(huì)返回代碼 LUA_YIELD ,并將線程置于一個(gè)后續(xù)可以恢復(fù)執(zhí)行的狀態(tài)中。

參數(shù):

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

返回值:

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

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

值傳遞:

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

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

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

在執(zhí)行協(xié)程體的 Lua 腳本時(shí),Lua 同樣可以調(diào)用 C++ 的函數(shù)。C++ 函數(shù)體內(nèi)也可以進(jìn)行協(xié)程掛起,通過(guò) 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)行時(shí),控制權(quán)會(huì)直接交給延續(xù)函數(shù) k 。當(dāng)協(xié)程交出控制后,調(diào)用 lua_yield 的函數(shù)后續(xù)語(yǔ)句就不會(huì)被執(zhí)行了。

參數(shù):

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

值得注意:

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

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

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

  • 創(chuàng)建一個(gè)新線程 lua_State ,在這個(gè)新線程中執(zhí)行協(xié)程體。
  • 協(xié)程體內(nèi),首先會(huì)在 Lua 腳本中掛起,并返回?cái)?shù)據(jù)到宿主中。
  • 恢復(fù)協(xié)程體后,Lua 從掛起點(diǎn)繼續(xù)運(yùn)行,調(diào)用 C++ 函數(shù),C++ 函數(shù)內(nèi)掛起協(xié)程體,并返回?cái)?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é)束,返回?cái)?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ù),會(huì)進(jìn)行掛起");

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

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

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

    return 1;
}

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

function foo(x)
    -- 協(xié)程掛起,并返回兩個(gè)數(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);

    // 返回值個(gè)數(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("返回值個(gè)數(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("返回值個(gè)數(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("返回值個(gè)數(shù): %d\n", result);
    printf("------------ L1 棧內(nèi)容:------------\n");
    stackDump(L1);

    lua_close(L);
}

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

LUA_YIELD=1
LUA_OK=0
第一次調(diào)用,Lua 腳本中掛起:
協(xié)程狀態(tài): LUA_YIELD
L1 棧深度: 2
返回值個(gè)數(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
返回值個(gè)數(shù): 1
------------ L1 棧內(nèi)容:------------
棧頂
^ typename: string, value: '調(diào)用 C++ 函數(shù),會(huì)進(jìn)行掛起'    
棧底

第三次調(diào)用,運(yùn)行 C++ 延續(xù)函數(shù),協(xié)程體結(jié)束::
協(xié)程狀態(tài): LUA_OK
L1 棧深度: 1
返回值個(gè)數(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);

描述:

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

舉個(gè)例子:

  • 創(chuàng)建一個(gè) lua_State "L",然后從這一 lua_State 創(chuàng)建一個(gè)新的線程 lua_State "L1"。
  • 通過(guò) L1 執(zhí)行 Lua 腳本,得到兩個(gè)數(shù)據(jù)。
  • 將 L1 的數(shù)據(jù)移動(dòng)到 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 個(gè)元素到 L 中(會(huì)將 L1 元素彈出)
    lua_xmove(L1, L, 2);

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

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

八、寫在最后

Lua 項(xiàng)目地址:Github傳送門

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

相關(guān)文章

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

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

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

    C語(yǔ)言實(shí)現(xiàn)2048小游戲

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

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

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

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

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

    c++歸并排序詳解

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

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

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

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

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

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

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

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

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

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

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

最新評(píng)論