Lua協程(一)

Lua協程(一)

本文主要涉及Lua協程是如何工作的,並不涉及Lua協程的工作原理。

本人理解能力有限,一直沒有弄懂Lua協程是如何工作的。今天,仔細看看了幫助文檔,有些感覺,記錄在此。

先來看看《Lua程序設計》中是如何講解Lua協程的:

協同程序與多線程的線程比較類似:有自己的堆棧,自己的局部變量,有自己的指令指針,但是和其它協同程序共享全局變量等很多信息。線程和協同程序的主要不同在於:在多處理器情況下,從概念上講多線程程序同時運行多個線程,而協同程序是通過協作來完成的,在任一指定時刻只有一個協同程序在運行,並且這個正在運行的協同程序只有在明確的被要求掛起的時候纔會被掛起。

lua通過table提供了所有的協同函數,create函數創建一個新的協同程序,create只有一個參數;協同程序將要運行的代碼封裝成函數,返回值爲thread類型的值表示創建了一個新的協同程序。協同有三個狀態:掛起態(suspended)、運行態(running)、死亡(dead)。注意的是:lua提供的協同是一種不對稱的協同,就是說掛起一個正在執行的協同的函數與使一個被掛起的協同再次執行的函數是不同的。

在來看官方參考手冊給的例子:

function foo (a)
   print("foo", a)
   return coroutine.yield(2*a)
end

co = coroutine.create(function (a,b)
   print("co-body", a, b)
   local r = foo(a+1)
   print("co-body", r)
   local r, s = coroutine.yield(a+b, a-b)
   print("co-body", r, s)
    return b, "end"
end)

print("main", coroutine.resume(co, 1, 10))
print("main", coroutine.resume(co, "r"))
print("main", coroutine.resume(co, "x", "y"))
print("main", coroutine.resume(co, "x", "y"))

output:
     co-body 1       10
     foo     2
     main    true    4
     co-body r
     main    true    11      -9
     co-body x       y
     main    true    10      end
     main    false   cannot resume dead coroutine

再來看看,參考文檔對coroutine.create coroutine.resume coroutine.yield幾個函數的解釋:

coroutine.create(f):創建一個新的協程,協程體中的內容是f,f必須是一個Lua函數。該函數返回這個新創建的類型爲thread的協程。

coroutine.resume(co [, val1, …]):當首次resume一個協程的時候,便開始運行這個協程中的函數f,參數val1, … 傳遞給函數f作爲f的參數。當協程掛起(yield)過,再次resume這個協程的時候,參數val1, … 作爲yield的返回結果;如果協程運行過程中沒有發生錯誤,那麼resume返回true以及yield傳遞過來的值(當協程掛起時),或者從函數f返回的任何值(當協程終止時)。如果協程運行過程中發生錯誤,那麼返回false以及錯誤信息。

coroutine.yield(…):將正在執行的線程掛起(暫停)。yield的任何參數將傳遞給resume,作爲resume額外的返回結果。

現在,我們來看看,上面的示例具體是如何運行的。

函數foo函數中有一個線程掛起,那麼它是對哪個線程進行掛起呢,上文引用中有說在任一指定時刻只有一個協同程序在運行,並且這個正在運行的協同程序只有在明確的被要求掛起的時候纔會被掛起,所以如果有某個協程程序調用了該函數,那麼該函數執行到yield時,會對這個正在運行的協程掛起。

co=coroutine.create創建了一個協程,協程創建後並不自動運行,可以看成在協程體中的函數f的入口處掛起了。

print('main', coroutine.resume(co, 1, 10)):第一次resume協程co,它的參數會作爲協程co中函數的參數,即a=1, b=10。此時運行協程co,執行到print('co-body', a, g)會打印出co-body 1 10。接着,執行到語句local r = foo(a+1),調用函數fooprint('foo', a)打印出foo 2;接着執行foo中的return coroutine.yield(2*a) 對當前的協程掛起,協程co就暫停在這裏,yield的參數返回到調用的resume的地方,此時print('main', coroutine.resume(co, 1, 10))打印出main true 4。從協程co開始執行,到第一次掛起時,所執行的流程(打印結果)如下:

cobody  1   10
foo     2
main    true    4

print('main', coroutine.resume(co, 'r')):再次重新啓用協程,此時協程還沒有死亡,繼續重新從上次yield處運行co中的函數。resume將參數‘r’傳給return coroutine.yield(2*a)作爲yield的返回結果,所以r='r',下面一條語句答應出co-body r。接着語句local r,s = coroutine.yield(a+b, a-b)又在此處掛起當前協程,yield將參數11 -9傳給調用此次resume的地方,print('main', coroutine.resume(co, 'r'))打印出main true 11 -9。從第二次resume,到第二期yield,所執行的流程(打印結果)如下:

co-body     r
main    true 11 -9

print('main', coroutine.resume(co, 'x', 'y')):再次重新啓用協程,此時協程還沒有死亡,繼續重新從上次yield處運行協程co中的函數。resume將參數x y傳給yield,作爲yield的返回結果,此時r='x', s='s'。接着print('co-body', r, s'打印出co-body r sreturn b, 'end'返回'10 end',協程終止(死亡)。print('main', coroutine.resume(co, 'x', 'y'))打印出main true 10 'end'。從第三次resume到協程終止,所執行的流程(打印結果)如下:

co-body x   y
main    true    10  end

print('main', coroutine.resume(co, 'x', 'y')):再次resume協程co,但此時協程co已經死亡,所以resume返回false,以及錯誤信息'cannot resume dead coroutine'。此次resume的流程如下:

main    false   cannot resume dead coroutine

總結下來,從協程創建,執行,到死亡的過程如下:
1. 創建協程,協程創建後並自動運行,可以看成在協程體中函數的入口處掛起。
2. 第一次resume協程時,resume的參數傳遞給協程體中的函數,該函數一直執行,直到初次遇到yield時掛起,此時協程體中的函數暫停在此處,保留協程體的堆棧,局部變量,指令指針等信息,以使協程下次重此處重新喚起。而yield的參數會作爲,resume的額外的返回結果。
3. 再次resume該協程時,resume的參數會作爲上次yield的返回結果,直到再次遇到yield時掛起。同步驟2,以此類推,知道協程終止。
4. 協程終止後,銷燬該協程的堆棧、局部變量與指令指針等信息。

參考
1. 《Lua程序設計》,Roberto Ierusalimschy,譯:周惟迪 。
2. Lua official website

發佈了57 篇原創文章 · 獲贊 9 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章