1. 程式人生 > >Lua的執行緒和狀態 及協程

Lua的執行緒和狀態 及協程

luaL_loadstring(L, "return coroutine.create(function() end)");
  nCallResult = lua_pcall(L, 0, 1, 0);

建立一個協程和lua_newthread建立一個執行緒一樣,不過這個建立會線上程的堆疊上壓入一個上面的function() end; lua_newthread只是建立一個執行緒,堆疊個數為0,面上面的個數為1,而且是function,使用lua_gettop和lua_type可得到
2014-09-08 分類:Lua 閱讀(2704) 評論(5) 

那不是真的多執行緒

Lua不支援真正的多執行緒,這句話我在《

Lua中的協同程式》這篇文章中就已經說了。根據我的程式設計經驗,在開發過程中,如果可以避免使用執行緒,那就堅決不用執行緒,如果實在沒有更好的辦法,那就只能退而用之。為什麼?首先,多個執行緒之間的通訊比較麻煩,同時,執行緒之間共享記憶體,對於共享資源的訪問,使用都是一個不好控制的問題;其次,執行緒之間來回切換,也會導致一些不可預估的問題,對效能也是一種損耗。Lua不支援真正的多執行緒,而是一種協作式的多執行緒,彼此之間協作完成,並不是搶佔完成任務,由於這種協作式的執行緒,因此可以避免由不可預知的執行緒切換所帶來的問題;另一方面,Lua的多個狀態之間不共享記憶體,這樣便為Lua中的併發操作提供了良好的基礎。

多個執行緒

從C API的角度來看,將執行緒想象成一個棧可能更形象些。從實現的觀點來看,一個執行緒的確就是一個棧。每個棧都保留著一個執行緒中所有未完成的函式呼叫資訊,這些資訊包括呼叫的函式、每個呼叫的引數和區域性變數。也就是說,一個棧擁有一個執行緒得以繼續執行的所有資訊。因此,多個執行緒就意味著多個獨立的棧。

當呼叫Lua C API中的大多數函式時,這些函式都作用於某個特定的棧。當我們呼叫lua_pushnumber時,就會將數字壓入一個棧中,那麼Lua是如何知道該使用哪個棧的呢?答案就在型別lua_State中。這些C API的第一個引數不僅表示了一個Lua狀態,還表示了一個記錄在該狀態中的執行緒。

只要建立一個Lua狀態,Lua就會自動在這個狀態中建立一個新執行緒,這個執行緒稱為“主執行緒”。主執行緒永遠不會被回收。當呼叫lua_close關閉狀態時,它會隨著狀態一起釋放。呼叫lua_newthread便可以在一個狀態中建立其他的執行緒。

lua_State *lua_newthread(lua_State *L);

這個函式返回一個lua_State指標,表示新建的執行緒。它會將新執行緒作為一個型別為“thread”的值壓入棧中。如果我們執行了:

L1 = lua_newthread(L);

現在,我們擁有了兩個執行緒L和L1,它們內部都引用了相同的Lua狀態。每個執行緒都有其自己的棧。新執行緒L1以一個空棧開始執行,老執行緒L的棧頂就是這個新執行緒。

除了主執行緒以外,其它執行緒和其它Lua物件一樣都是垃圾回收的物件。當新建一個執行緒時,執行緒會壓入棧,這樣能確保新執行緒不會成為垃圾,而有的時候,你在處理棧中資料時,不經意間就把執行緒彈出棧了,而當你再次使用該執行緒時,可能導致找不到對應的執行緒而程式崩潰。為了避免這種情況的發生,可以保持一個對執行緒的引用,比如在登錄檔中儲存一個對執行緒的引用。

當擁有了一個執行緒以後,我們就可以像主執行緒那樣來使用它,以前博文中提到的對棧的操作,對這個新的執行緒都適用。然而,使用多執行緒的目的不是為了實現這些簡單的功能,而是為了實現協同程式。

為了掛起某些協同程式的執行,並在稍後恢復執行,我們可以使用lua_resume函式來實現。

int lua_resume(lua_State *L,int narg);

lua_resume可以啟動一個協同程式,它的用法就像lua_call一樣。將待呼叫的函式壓入棧中,並壓入其引數,最後在呼叫lua_resume時傳入引數的數量narg。這個行為與lua_pcall類似,但有3點不同。

  1. lua_resume沒有引數用於指出期望的結果數量,它總是返回被呼叫函式的所有結果;
  2. 它沒有用於指定錯誤處理函式的引數,發生錯誤時不會展開棧,這就可以在發生錯誤後檢查棧中的情況;
  3. 如果正在執行的函式交出(yield)了控制權,lua_resume就會返回一個特殊的程式碼LUA_YIELD,並將執行緒置於一個可以被再次恢復執行的狀態。

當lua_resume返回LUA_YIELD時,執行緒的棧中只能看到交出控制權時所傳遞的那些值。呼叫lua_gettop則會返回這些值的數量。若要將這些值移到另一個執行緒,可以使用lua_xmove。

為了恢復一個掛起執行緒的執行,可以再次呼叫lua_resume。在這種呼叫中,Lua假設棧中所有的值都是由yield呼叫返回的,當然了,你也可以任意修改棧中的值。作為一個特例,如果在一個lua_resume返回後與再次呼叫lua_resume之間沒有改變過執行緒棧中的內容,那麼yield恰好返回它交出的值。如果能很好的理解這個特例是什麼意思,那就說明你已經非常理解Lua中的協同程式了,如果你還是不知道我說的這個特例是什麼意思,請再去讀一遍《Lua中的協同程式》,如果你還不懂,那你就在下放留言吧(提醒:這個特例主要利用的是resume-yield之間的傳參規則)。

現在,我就通過一個簡單的程式來做個試驗,以便更好的理解Lua的執行緒。使用C程式碼來呼叫Lua指令碼,Lua函式作為一個協同程式來啟動,這個Lua函式可以呼叫其它Lua函式,任意的一個Lua函式都可以交出控制權,從而使lua_resume呼叫返回。對於使用C呼叫Lua不熟悉的夥計,請再去仔細的讀讀《Lua與C》和《C“控制”Lua》這兩篇文章吧。先貼上重要的程式碼吧。下面是Lua程式碼:

functionFunc1(param1)Func2(param1 +10)print("Func1 ended.")return30endfunctionFunc2(value)
    coroutine.yield(10, value)print("Func2 ended.")end

下面是C++程式碼:

lua_State *L1 = lua_newthread(L);if(!L1){return0;}

lua_getglobal(L1,"Func1");
lua_pushinteger(L1,10);// 執行這個協同程式// 這裡返回LUA_YIELD
bRet = lua_resume(L1,1);
cout <<"bRet:"<< bRet << endl;// 列印L1棧中元素的個數
cout <<"Element Num:"<< lua_gettop(L1)<< endl;// 列印yield返回的兩個值
cout <<"Value 1:"<< lua_tointeger(L1,-2)<< endl;
cout <<"Value 2:"<< lua_tointeger(L1,-1)<< endl;// 再次啟動協同程式// 這裡返回0
bRet = lua_resume(L1,0);
cout <<"bRet:"<< bRet << endl;
cout <<"Element Num:"<< lua_gettop(L1)<< endl;
cout <<"Value 1:"<< lua_tointeger(L1,-1)<< endl;

上面的程式,你可以先執行一下;你能想到執行結果麼?單擊這裡下載完整工程LuaThreadDemo.zip

上面的例子是C語言呼叫Lua程式碼,Lua可以自己掛起自己;如果Lua去呼叫C程式碼呢?C函式不能自己掛起它自己,一個C函式只有在返回時,才會交出控制權。因此C函式實際上是不會停止自身執行的,不過它的呼叫者可以是一個Lua函式,那麼這個C函式呼叫lua_yield,就可以掛起Lua呼叫者:

int lua_yield(lua_State *L,int nresults);

你沒有聽錯,C程式碼呼叫lua_yield不能掛起自己,但是它卻可以將它的Lua呼叫者掛起。其中nresults是準備返回給相應resume的棧頂值的個數,當協同程式再次恢復執行時,Lua呼叫者會收到傳遞給resume的值。lua_yield在使用時,只能作為一個返回的表示式,而不能獨自使用。比如:

return lua_yield(L,0);

對於多執行緒程式設計,本身就是麻煩的問題,而這裡枯燥的文字總結,也會沒有效果,下面來一個簡短的例子。先貼Lua程式碼,這段程式碼需要結合C程式碼一起看,否則就是雲裡霧裡的。

require"lua_yieldDemo"local function1 =function()local value
    repeat
      value =Module.Func1()until value
    return value
endlocal thread1 = coroutine.create(function1)--現在執行到了Module.Func1()--100這個值將會被賦值給value
coroutine.resume(thread1)--print(coroutine.status(thread1))--設定C函式環境Module.Func2(10)print(coroutine.resume(thread1))

C程式碼如下:

// 判斷環境表中JellyThink是否被設定了staticintIsSet(lua_State *L){
	lua_getfield(L, LUA_ENVIRONINDEX,"JellyThink");if(lua_isnil(L,-1)){
		printf("Not set\n");return0;}return1;}staticintFunc1(lua_State *L){// 沒有被設定就掛起if(!IsSet(L)){
		printf("Begin yield\n");return lua_yield(L,0);}// 被設定了,就取值,返回被設定的值
	printf("Resumed again\n");
	lua_getfield(L, LUA_ENVIRONINDEX,"JellyThink");return1;}// 設定JellThink的值staticintFunc2(lua_State *L){
	luaL_checkinteger(L,1);// 設定到環境表中
	lua_pushvalue(L,1);
	lua_setfield(L, LUA_ENVIRONINDEX,"JellyThink");return0;}

當我在Lua中呼叫coroutine.resume時,我都只傳遞了一個引數,其它引數都沒有;這裡需要注意,如果我傳值了,就相當於給value賦值了。當我恢復thread1執行時,它是從Module.Func1()返回處繼續執行,也就是對value賦值,而這裡賦予value的值實際上是傳給resume的值。上面的程式碼中,我沒有傳值,如果傳了,就無法驗證我設定的10了。單擊這裡下載完整工程lua_yieldDemo.zip。Any question? No? OK, Next.

Lua狀態

每次呼叫luaL_newstate(或者lua_newstate)都會建立一個新的Lua狀態。不同的Lua狀態是各自完全獨立的,它們之間不共享任何資料。這個概念是不是很熟悉,是不是特別像Windows中的程序的概念。也就是說,在一個Lua狀態中發生的錯誤也不會影響其它的的Lua狀態,windows的程序也是這樣的。並且,Lua狀態之間不能直接溝通,必須寫一些輔助程式碼來完成這點。

由於所有交換的資料必須經由C程式碼中轉,所以只能在Lua狀態間交換那些可以在C語言中表示的型別,例如字串和數字。由於Lua狀態我目前沒有使用過,也就沒有足夠的信心和資格去總結這個東西,還是怕會誤導大家,如果以後在實際專案中使用了Lua狀態,我還會回過頭來總結Lua狀態的。相信我,我還會回來的。

總結

哦,這篇文章拖的時間夠長的啊。由於最近專案緊,趕著上線,很忙啊,加班啊。又趕上中秋節,也沒有太多的時間來寫。這篇就這樣的,對於Lua狀態的總結,還是不夠深刻,或者說,基本就沒有。哦,算了,後續在總結吧,也不能,也不可能一口吃成一個胖子的。中秋快樂,各位。

2014年9月8日 於深圳。

int running = 1; 


int lua_finish(lua_State * L) { 
running = 0; 
printf("lua_finish called\n"); 
return 0; 

int lua_sleep(lua_State *L) { 
printf("lua_sleep called\n"); 
//lua_pushnumber(L, 10); 
return lua_yield(L, 0); 



int main() { 
lua_State* L = luaL_newstate(); 
luaL_openlibs(L); 


lua_register(L, "sleep", lua_sleep); 
lua_register(L, "finish", lua_finish); 


//luaL_dofile(L, "scripts/init.lua"); 


lua_State* cL = lua_newthread(L); 
luaL_loadfile(cL, "loop.lua"); 


while (running) { 
int status; 
lua_pushnumber(cL, 20); 
status = lua_resume(cL, 1); 
if (status == LUA_YIELD) { 
printf("loop yielding\n"); 

else { 
running = 0; // you can't try to resume if it didn't yield 
// catch any errors below 
if (status == LUA_ERRRUN && lua_isstring(cL, -1)) { 
printf("isstring: %s\n", lua_tostring(cL, -1)); 
lua_pop(cL, -1); 





//luaL_dofile(L, "scripts/end.lua"); 
lua_close(L); 
return 0; 

}

lua file

print("loop.lua")


local i = 0
while true do
    print("lua_loop iteration")
    local bret = sleep()
print(bret)


    i = i + 1
    if i == 4 then
        break
    end
end


finish()

 Lua中的協程和多執行緒很相似,每一個協程有自己的堆疊,自己的區域性變數,可以通過yield-resume實現在協程間的切換。不同之處是:Lua協程是非搶佔式的多執行緒,必須手動在不同的協程間切換,且同一時刻只能有一個協程在執行。並且Lua中的協程無法在外部將其停止,而且有可能導致程式阻塞。

協同程式(Coroutine):

  三個狀態:suspended(掛起,協同剛建立完成時或者yield之後)、running(執行)、dead(函式走完後的狀態,這時候不能再重新resume)。

  coroutine.create(arg):根據一個函式建立一個協同程式,引數為一個函式

  coroutine.resume(co):使協同從掛起變為執行(1)啟用coroutine,也就是讓協程函式開始執行;(2)喚醒yield,使掛起的協同接著上次的地方繼續執行。該函式可以傳入引數

  coroutine.status(co):檢視協同狀態

  coroutine.yield():使正在執行的協同掛起,可以傳入引數

  resume函式的兩種用途雖然都是使協同掛起,但還是有些許差異的,看下面這個例子:

複製程式碼
coroutineFunc = function (a, b) 
    
            
           

相關推薦

Lua執行狀態

luaL_loadstring(L, "return coroutine.create(function() end)");   nCallResult = lua_pcall(L, 0, 1, 0); 建立一個協程和lua_newthread建立一個執行緒一

執行,程序,,非同步同步,非阻塞IO

1.執行緒,程序,協程 程序定義:程序是具有一定獨立功能的程式在一個數據集上的一次動態執行的過程,是系統進行資源分配和排程的一個獨立單位 執行緒定義:執行緒是CPU排程和分派的基本單位,是比程序更小能獨立執行的單位,執行緒佔有系統。但是它可以與它同屬的程序和其他在該程序中的執行緒共享

執行、程序、GIL(三)

上一篇文章介紹了:建立縣城的兩種方式、Event物件判斷執行緒是否啟動、利用訊號量控制執行緒併發。 部落格連結:執行緒、程序、協程和GIL(二) 這一篇來說說執行緒間通訊的那些事兒:    一個執行緒向另一個執行緒傳送資料最安全的方式就是使用queue庫中的隊列了,通過建立一個供多個執行緒共享

程序執行狀態轉換

執行緒從建立、執行到結束總是處於下面五個狀態之一:新建狀態、就緒狀態、執行狀態、阻塞狀態及死亡狀態。     1.新建狀態(New):         當用new操作符建立一個執行緒時, 例如new Thread(r),執行緒還沒有開始執行,此時執行緒處在新建狀態。 當一

理解一下Python中的多執行,多程序,多

程序 一個執行的程式(程式碼)就是一個程序,沒有執行的程式碼叫程式,程序是系統資源分配的最小單位,程序擁有自己獨立的記憶體空間,所以程序間資料不共享,開銷大。 執行緒, 排程執行的最小單位,也叫執行路徑,不能獨立存在,依賴程序存在一個程序至少有一個執行緒,叫主執行緒,而多

python中的多程序,多執行,死鎖,多

本人根據自己的理解來總結的,如果有錯誤的地方還請各位大佬指正,謝謝了. 程序:程式是計算機可執行的二進位制資料,只有被作業系統呼叫的時候才開始它們的生命週期.程序就是程式的一次執行,擁有自己的地址空間,記憶體,程序id(pid),資料棧及其他記錄其執行軌跡的輔助資料;最小的

python學習筆記 ---執行、程序、、佇列、python-memcache、python-redis

一、執行緒 Threading用於提供執行緒相關的操作,執行緒是應用程式中工作的最小單元。 #!/usr/bin/env python # -*- coding:utf-8 -*- import threading import time def show(arg): time.

java必會基礎知識之執行狀態切換

很多初學java的程式設計師在學習執行緒的時候總是學的一知半解,但是執行緒在java學習中又佔據著不可替代的位置,所以我們必須把執行緒學好、學透、學精,這樣才能在java學習的路上越走越遠。 首先我們來說下執行緒的幾種狀態,Java中執行緒的狀態分為6種: 1. 初始狀態 初始狀態

一起分析執行狀態執行通訊機制

> 本文在個人技術部落格同步釋出,詳情可[**用力戳**](http://www.17coding.info/article/27) > 亦可掃描螢幕右側二維碼關注個人公眾號,公眾號內有個人聯絡方式,等你來撩...   多執行緒程式設計一直是普通程式設計師進階為高階程式設計師的必備技

程序、執行

一、程序 1、多工原理   多工是指作業系統同時可以執行多個任務。 單核CPU實現多工原理:作業系統輪流讓各個任務交替執行; 多核CPU實現多工原理:真正的執行多工只能在多核CPU上實現,多出來的任務輪流排程到每個核心上執行。 併發:看上去一起執行,任務數多

淺談程序、執行三者之間的區別聯絡

一、程序、執行緒、協程 1,程序 經典定義:一個執行中程式的例項。系統中的每個程式都執行在某個程序的上下文中。(-摘自 CSAPP) 程序是系統資源分配的最小單位   2,執行緒(thread) 執行緒就是執行在程序上下文中的邏輯流。 執行緒是作業系統能夠進行運算排程的最小單位。 &

執行

執行緒 初識執行緒: 輕量級程序,直接被cpu排程 不能獨立存在的輕量級程序 同一個程序中的多個執行緒之間的資料共享 執行緒和程序的關係: 執行緒和程序的區別可以歸納為以下4點: 地址空間和其他資源(如開啟檔案):程序間相互獨立,同一個程序的各執行緒間共享.某程序內的執行

簡述程序,執行

執行緒,程序和協程 執行緒 執行緒的概念 併發 任務數大於cpu核載,通過系統的各種任務跳讀演算法,是任務“在一起”執行任務! 假的多工 並行 任務數小於cpu核數,即任務真的在一起執行 多執行緒 1 同時執行 下面例子中test1和test2是同時執行 import threadi

一個故事講完程序、執行

很久以前,有兩個程式,暫且稱他們旺財和小強吧。旺財和小強這兩個程式都很長,每個都有十幾萬行。 他們兩個的人生價值就是到CPU上去執行,把執行結果告訴人類。CPU是稀缺資源,只有一個,他們倆必須排著隊,輪流使用。旺財從頭到尾執行完了,讓出CPU, 讓小強從頭兒去執行。人類把這種處理方式叫做批處理。程序長久以來,

.執行程序的補充

執行緒:threading提供執行緒相關的操作。執行緒是應用程式工作的做小單位,它包含在程序中,是程序中的實際執行單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每個執行緒並執行不同的任務。    threading模組建立在-thread之上,thread以低階的最原始的方

python併發程式設計之執行剩餘內容(執行佇列,執行池)

1. 執行緒的其他方法 import threading import time from threading import Thread,current_thread def f1(n): time.sleep(1) print('子執行緒名稱', current_thread()

關於程序、執行 的基本概念

程序、執行緒和協程 GIL執行緒全域性鎖 執行緒全域性鎖(Global Interpreter Lock),即Python為了保證執行緒安全而採取的獨立執行緒執行的限制,說白了就是一個核只能在同一時間執行一個執行緒.對於io密集型任務,python的多執

Golang:執行 的區別

作者:林冠巨集 / 指尖下的幽靈 部落格:http://www.cnblogs.com/linguanh/ GitHub : https://github.com/af913337456/ 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8

flask之分析執行

flask之分析執行緒和協程 01 思考:每個請求之間的關係 我們每一個請求進來的時候都開一個程序肯定不合理,那麼如果每一個請求進來都是序列的,那麼根本實現不了併發,所以我們假定每一個請求進來使用的是執行緒。 那麼執行緒中資料互相不隔離,存在修改資料的時候資料不安全的問題。 假定我們的需求是,每個執行緒都要設

深入分析 Java、Kotlin、Go 的執行

![](https://img2020.cnblogs.com/other/633265/202012/633265-20201211165836556-115473640.jpg) - [前言](#前言) - [協程是什麼](#協程是什麼) - [協程的好處](#協程的好處) - [程序](#程序