第一版是為 Lua 5.0 編寫的。儘管在很大程度上仍然適用於後續版本,但仍有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您還可以 支持 Lua 專案


9.2 – 管道和篩選器

協程最具代表性的範例之一在於生產者-消費者問題。假設我們有一個持續產生值的函式(例如,從檔案讀取值),以及另一個持續消耗這些值的函式(例如,將它們寫入另一個檔案)。通常,這兩個函式看起來像這樣

    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
(在該實作中,生產者和消費者都會永遠執行。要將它們變更為在沒有更多資料要處理時停止,是一件很容易的事。)這裡的問題是如何將 sendreceive 相匹配。這是誰擁有主迴圈問題的典型案例。生產者和消費者都很活躍,都有自己的主迴圈,而且都假設另一個是可呼叫服務。對於這個特定範例,很容易變更其中一個函式的結構,展開其迴圈並使其成為被動代理。然而,在其他實際場景中,這種結構變更可能遠非易事。

協程提供了匹配生產者和消費者的理想工具,因為恢復-讓步配對顛覆了呼叫者和被呼叫者之間的典型關係。當協程呼叫 yield 時,它不會進入新的函式;而是傳回一個待處理呼叫(給 resume)。類似地,呼叫 resume 不會啟動新的函式,而是傳回對 yield 的呼叫。這個特性正是我們需要的方式,以匹配 sendreceive,讓每個函式都像自己是主人,而另一個是奴隸。因此,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 管道,你並不孤單。畢竟,協程是一種(非搶先式)多執行緒。在管道中,每個任務在一個獨立的程序中執行,而在協程中,每個任務在一個獨立的協程中執行。管道在寫入器(生產者)和讀取器(消費者)之間提供一個緩衝區,因此它們的相對速度有一些自由度。這在管道的脈絡中很重要,因為在程序之間切換的成本很高。使用協程,在任務之間切換的成本小得多(大約等於函式呼叫的成本),因此寫入器和讀取器可以齊頭並進。