第一版是為 Lua 5.0 編寫的。儘管對於後續版本而言仍然相當相關,但有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買這本書,您也可以協助 支援 Lua 專案


9.1 – 協程基礎

Lua 將所有協程函式封裝在 coroutine 表格中。create 函式用來建立新的協程。它有一個單一引數,一個包含協程將執行的程式碼的函式。它會傳回一個類型為 thread 的值,代表新的協程。通常,create 的引數是一個匿名函式,如下所示

    co = coroutine.create(function ()
           print("hi")
         end)
    
    print(co)   --> thread: 0x8071d98

協程可以處於三種不同狀態之一:暫停、執行和結束。當我們建立一個協程時,它會從暫停狀態開始。這表示協程在我們建立它時不會自動執行其主體。我們可以使用 status 函式來檢查協程的狀態

    print(coroutine.status(co))   --> suspended
函式 coroutine.resume 會(重新)開始執行一個協程,將其狀態從暫停變更為執行
    coroutine.resume(co)   --> hi
在此範例中,協程主體只會印出 "hi" 並終止,讓協程處於結束狀態,無法再傳回
    print(coroutine.status(co))   --> dead

到目前為止,協程看起來只不過是一種複雜的函式呼叫方式。協程的真正力量來自 yield 函式,它允許正在執行的協程暫停其執行,以便稍後可以繼續執行。讓我們看一個簡單的範例

    co = coroutine.create(function ()
           for i=1,10 do
             print("co", i)
             coroutine.yield()
           end
         end)
現在,當我們繼續執行此協程時,它會開始執行並執行到第一個 yield
    coroutine.resume(co)    --> co   1
如果我們檢查其狀態,我們可以看到協程已暫停,因此可以再次繼續執行
    print(coroutine.status(co))   --> suspended
從協程的角度來看,當協程暫停時發生的所有活動都發生在它呼叫yield的過程中。當我們恢復協程時,這個呼叫yield的動作最後會回傳,而協程會繼續執行,直到下一個 yield 或結束
    coroutine.resume(co)    --> co   2
    coroutine.resume(co)    --> co   3
    ...
    coroutine.resume(co)    --> co   10
    coroutine.resume(co)    -- prints nothing
在最後一次呼叫resume時,協程主體完成迴圈後回傳,因此協程現在已經結束了。如果我們再次嘗試恢復它,resume會回傳false以及錯誤訊息
    print(coroutine.resume(co))
    --> false   cannot resume dead coroutine
請注意,resume會在受保護模式下執行。因此,如果協程內部有任何錯誤,Lua不會顯示錯誤訊息,而是會將錯誤訊息回傳給resume呼叫。

Lua中一個有用的功能是,resume-yield配對可以在它們之間交換資料。第一個resume,沒有對應的yield在等待它,會將它的額外參數作為協程主函式的參數傳遞

    co = coroutine.create(function (a,b,c)
           print("co", a,b,c)
         end)
    coroutine.resume(co, 1, 2, 3)    --> co  1  2  3
呼叫resume會在true(表示沒有錯誤)之後回傳傳遞給對應yield的任何參數
    co = coroutine.create(function (a,b)
           coroutine.yield(a + b, a - b)
         end)
    print(coroutine.resume(co, 20, 10))  --> true  30  10
對稱地,yield會回傳傳遞給對應resume的任何額外參數
    co = coroutine.create (function ()
           print("co", coroutine.yield())
         end)
    coroutine.resume(co)
    coroutine.resume(co, 4, 5)     --> co  4  5
最後,當協程結束時,它的主函式回傳的任何值都會傳遞給對應的resume
    co = coroutine.create(function ()
           return 6, 7
         end)
    print(coroutine.resume(co))   --> true  6  7

我們很少在同一個協程中使用所有這些功能,但它們都有各自的用途。

對於那些已經對協程有所了解的人來說,在我們繼續之前,釐清一些概念非常重要。Lua提供了我稱之為非對稱協程的功能。這表示它有一個函式可以暫停協程的執行,以及另一個函式可以恢復暫停的協程。其他一些語言提供對稱協程,其中只有一個函式可以將控制權從一個協程傳遞到另一個協程。

有些人稱非對稱協程為半協程(因為它們不對稱,所以它們並非真正的協同)。然而,其他人使用相同的術語半協程來表示協程的受限實作,其中協程只能在它不在任何輔助函式內部時暫停執行,也就是說,當它在控制堆疊中沒有待處理的呼叫時。換句話說,只有這種半協程的主體才能 yield。Python 中的產生器就是這種意義上的半協程的一個例子。

與對稱和非對稱協程之間的差異不同,協程和產生器(如 Python 中所呈現)之間的差異很深;產生器根本不夠強大,無法實作我們可以用真正的協程編寫的幾個有趣的結構。Lua 提供真正的非對稱協程。那些偏好對稱協程的人可以在 Lua 的非對稱設施之上實作它們。這是一項簡單的任務。(基本上,每個傳輸都會執行一個 yield,然後執行一個 resume。)