Coroutine 教程

lua-users home
wiki

Coroutine 是什麼?

Coroutine 讓我們一次執行多個工作。這是通過有控制地將控制權傳遞給每個常式並等待常式表明它已完成執行來完成的。然後,我們可以重新進入常式以稍後繼續執行,通過重複此操作,我們實現了多工作處理。

Coroutine 在參考手冊的第 2.11 和 5.2 節中進行了說明。[1] [2]

多執行緒

每個工作在執行緒中執行,該執行緒與其他執行緒分開。一次執行多個工作通常稱為多執行緒。因為一次執行多個執行緒,所以我們的應用程式稱為多執行緒的。

有多種方法可以實作多執行緒。有些系統會為每個執行緒分配固定的時間,並在時間到時收回控制權,並將控制權傳給下一個執行緒,依此類推。這種方式稱為優先搶佔式多執行緒。在這種情況下,每個執行緒不需要擔心它佔用的時間,它更注重自己的功能。

在其他系統中,一個執行緒關注它執行的時間長度。執行緒知道它必須將控制權傳給其他執行緒,以便它們也能夠執行。這種方式稱為協調式共同作業式多執行緒。在此,所有執行緒都相互協調,以允許應用程式正常運作。這是 Lua 的 coroutine 使用的多工作處理類型。

Lua 中的 Coroutine 不是作業系統執行緒或程序。Coroutine 是在 Lua 中建立的 Lua 程式碼區塊,並且有類似於執行緒的自己的控制流程。一次只執行一個 coroutine,執行直到它啟動另一個 coroutine 或讓出(傳回呼叫它的 coroutine)為止。Coroutine 是一種以便利且自然的方式表達多個共同作業的控制執行緒,但它們不會並行執行,因此從多個 CPU 中不會獲得效能上的好處。然而,由於 coroutine 切換的速度遠快於作業系統執行緒,並且通常不需要複雜且有時昂貴的鎖定機制,因此使用 coroutine 通常比使用完整作業系統執行緒的等效程式更快。

讓步

為了讓多個 coroutine 共享執行,它們必須停止執行(在執行合理的處理工作之後)並將控制權傳遞給另一個執行緒。這種提交行為稱為讓步。Coroutine 明確呼叫 Lua 函式 coroutine.yield(),這類似於在函式中使用 return。區分讓步與函式傳回的是在稍後我們可以在執行緒中重新進入並從我們離開的位置繼續執行。當您使用 return 退出函式範圍時會銷毀該範圍,我們無法重新進入該範圍,例如:

> function foo(x)
>>  if x>3 then return true end  -- we can exit the function before the end if need be
>>  return false                 -- return a value at the end of the function (optional)
>> end
> = foo(1)
false
> = foo(100)                     -- different exit point
true

簡單用法

要建立 coroutine,我們必須有一個代表它的函式,例如:

> function foo()
>>   print("foo", 1)
>>   coroutine.yield()
>>   print("foo", 2)
>> end
>

我們使用 coroutine.create(fn) 函式建立協程。我們傳遞一個 Lua 函式給它,作為執行緒的進入點。Lua 回傳的物件是一個 執行緒

> co = coroutine.create(foo) -- create a coroutine with foo as the entry
> = type(co)                 -- display the type of object "co"
thread

我們可以使用 coroutine.status() 函式找出執行緒目前的狀態,例如:

> = coroutine.status(co)
suspended
已暫停 狀態表示執行緒是活動的,並且正如你所預期的,它什麼都沒在做。請注意,當我們建立執行緒時,它並沒有開始執行。我們使用 coroutine.resume() 函式開始執行緒。當執行緒讓出時,Lua 會進入執行緒並離開執行緒。
> = coroutine.resume(co)
foo     1
true
coroutine.resume() 函式會回傳續用的錯誤狀態。輸出顯示我們進入了 foo 函式,並且沒有錯誤地離開。現在是最有趣的部分。如果使用函式,我們無法從離開處繼續,但使用協程,我們可以再次恢復
> = coroutine.resume(co)
foo     2
true
我們可以看到,我們執行了 foo 中讓出之後的行,並且再次沒有錯誤地回傳。但是,如果我們查看狀態,我們可以看到我們離開了 foo 函式,並且協程終止了。
> = coroutine.status(co)
dead
如果我們再次嘗試續用,將會回傳一對值:一個錯誤標記和一個錯誤訊息
> = coroutine.resume(co)
false   cannot resume dead coroutine
一旦協程離開或比函式回傳,就無法再次續用。

更多詳細資訊

以下是一個示範協程一些重要功能的更複雜範例。

> function odd(x)
>>   print('A: odd', x)
>>   coroutine.yield(x)
>>   print('B: odd', x)
>> end
>
> function even(x)
>>   print('C: even', x)
>>   if x==2 then return x end
>>   print('D: even ', x)
>> end
>
> co = coroutine.create(
>>   function (x)
>>     for i=1,x do
>>       if i==3 then coroutine.yield(-1) end
>>       if i % 2 == 0 then even(i) else odd(i) end
>>     end
>>   end)
>
> count = 1
> while coroutine.status(co) ~= 'dead' do
>>   print('----', count) ; count = count+1
>>   errorfree, value = coroutine.resume(co, 5)
>>   print('E: errorfree, value, status', errorfree, value, coroutine.status(co))
>> end
----    1
A: odd  1
E: errorfree, value, status     true    1       suspended
----    2
B: odd  1
C: even 2
E: errorfree, value, status     true    -1      suspended
----    3
A: odd  3
E: errorfree, value, status     true    3       suspended
----    4
B: odd  3
C: even 4
D: even         4
A: odd  5
E: errorfree, value, status     true    5       suspended
----    5
B: odd  5
E: errorfree, value, status     true    nil     dead
>

基本上,我們有一個 for 迴圈,它在遇到奇數時呼叫 odd() 函式,在遇到偶數時呼叫 even() 函式。輸出可能有點難理解,因此我們將逐一研究以 count 計數的外層迴圈。已新增註解。

----    1
A: odd  1       -- yield from odd()
E: errorfree, value, status     true    1       suspended
在迴圈一中,我們使用 coroutine.resume(co, 5) 呼叫我們的協程。我們第一次呼叫它時,我們進入協程函式中的 for 迴圈。請注意,我們的協程函式所呼叫的 odd() 函式會讓出。你不必在協程函式中讓出。這是一個重要且有用的功能。我們用讓出回傳 1 的值。

----    2
B: odd  1       -- resume in odd with the values we left on the yield
C: even 2       -- call even and exit prematurely
E: errorfree, value, status     true    -1      suspended  -- yield in for loop
在迴圈 2 中,主要的 for 迴圈會讓出,並暫停協程。這裡要注意的一點是,我們可以在任何地方讓出。我們不必一直從協程中的某一點讓出。我們用讓出回傳 -1。

----    3
A: odd  3       -- odd() yields again after resuming in for loop
E: errorfree, value, status     true    3       suspended
我們在 for 迴圈中恢復協程,並且在呼叫 odd() 時,它再次讓出。

----    4
B: odd  3       -- resume in odd(), variable values retained
C: even 4       -- even called()
D: even 4       -- no return in even() this time
A: odd  5       -- odd() called and a yield
E: errorfree, value, status     true    5       suspended
在迴圈 4 中,我們從 odd() 的中斷處恢復執行。請注意,變數值會被保留。在協程暫停期間,odd() 函式的範圍會被保留。我們遍历到 even() 的結尾,這次在函式結尾處離開。無論如何,當我們不使用 coroutine.yield() 離開函式時,範圍和其所有變數都會被銷毀。我們只能在讓出時恢復執行。

----    5
B: odd  5       -- odd called again
E: errorfree, value, status     true    nil     dead  -- for loop terminates
>
我們再次恢復執行 odd()。這次,主要的 for 迴圈達到我們傳遞給 coroutine 的 5 的極限。5 的值和 for 迴圈狀態在 coroutine 的執行過程中都被保留。coroutine 在存在時保留自己的堆疊和狀態。當我們離開 coroutine 函式時,它便會結束,我們無法再使用它。


最近變更 · 偏好設定
編輯 · 歷史
上一次編輯在 2017 年 8 月 12 日下午 11:51(格林威治標準時間) (diff)