第一版是為 Lua 5.0 編寫的。儘管在很大程度上仍然適用於後續版本,但仍有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您還可以 支持 Lua 專案。
![]() |
程式設計 Lua | ![]() |
第一部分。語言 第 9 章。協程 |
協程最具代表性的範例之一在於生產者-消費者問題。假設我們有一個持續產生值的函式(例如,從檔案讀取值),以及另一個持續消耗這些值的函式(例如,將它們寫入另一個檔案)。通常,這兩個函式看起來像這樣
function producer () while true do local x = io.read() -- produce new value send(x) -- send to consumer end end function consumer () while true do local x = receive() -- receive from producer io.write(x, "\n") -- consume new value end end(在該實作中,生產者和消費者都會永遠執行。要將它們變更為在沒有更多資料要處理時停止,是一件很容易的事。)這裡的問題是如何將
send
與 receive
相匹配。這是誰擁有主迴圈問題的典型案例。生產者和消費者都很活躍,都有自己的主迴圈,而且都假設另一個是可呼叫服務。對於這個特定範例,很容易變更其中一個函式的結構,展開其迴圈並使其成為被動代理。然而,在其他實際場景中,這種結構變更可能遠非易事。
協程提供了匹配生產者和消費者的理想工具,因為恢復-讓步配對顛覆了呼叫者和被呼叫者之間的典型關係。當協程呼叫 yield
時,它不會進入新的函式;而是傳回一個待處理呼叫(給 resume
)。類似地,呼叫 resume
不會啟動新的函式,而是傳回對 yield
的呼叫。這個特性正是我們需要的方式,以匹配 send
和 receive
,讓每個函式都像自己是主人,而另一個是奴隸。因此,receive
恢復生產者,以便它可以產生一個新值;而 send
將新值讓步回消費者
function receive () local status, value = coroutine.resume(producer) return value end function send (x) coroutine.yield(x) end當然,生產者現在必須是一個協程
producer = coroutine.create( function () while true do local x = io.read() -- produce new value send(x) end end)在此設計中,程式會開始呼叫消費者。當消費者需要一個項目時,它會繼續執行生產者,生產者會執行直到它有一個項目可以提供給消費者,然後停止直到消費者再次重新啟動它。因此,我們有我們所謂的消費者驅動設計。
我們可以使用過濾器來延伸這個設計,過濾器是介於生產者和消費者之間,對資料進行某種轉換的任務。過濾器同時是一個消費者和生產者,因此它會繼續執行一個生產者以取得新值,並將轉換後的數值傳遞給消費者。一個微不足道的範例,我們可以在先前的程式碼中加入一個過濾器,在每行的開頭插入一個行號。完整的程式碼會像這樣
function receive (prod) local status, value = coroutine.resume(prod) return value end function send (x) coroutine.yield(x) end function producer () return coroutine.create(function () while true do local x = io.read() -- produce new value send(x) end end) end function filter (prod) return coroutine.create(function () local line = 1 while true do local x = receive(prod) -- get new value x = string.format("%5d %s", line, x) send(x) -- send it to consumer line = line + 1 end end) end function consumer (prod) while true do local x = receive(prod) -- get new value io.write(x, "\n") -- consume new value end end最後一個位元只會建立它需要的組件,將它們連接起來,並啟動最後的消費者
p = producer() f = filter(p) consumer(f)或更好的是
consumer(filter(producer()))
如果你在讀完先前的範例後,想到了 Unix 管道,你並不孤單。畢竟,協程是一種(非搶先式)多執行緒。在管道中,每個任務在一個獨立的程序中執行,而在協程中,每個任務在一個獨立的協程中執行。管道在寫入器(生產者)和讀取器(消費者)之間提供一個緩衝區,因此它們的相對速度有一些自由度。這在管道的脈絡中很重要,因為在程序之間切換的成本很高。使用協程,在任務之間切換的成本小得多(大約等於函式呼叫的成本),因此寫入器和讀取器可以齊頭並進。
版權所有 © 2003–2004 Roberto Ierusalimschy。保留所有權利。 | ![]() |