初版は Lua 5.0 向けに書かれました。後のバージョンにも大部分で関連していますが、違いがいくつかあります。
第 4 版は Lua 5.3 を対象としており、Amazon や他の書店で入手できます。
この本を購入することで、Lua プロジェクト のサポートにも協力できます。


9.2 – パイプとフィルタ

コルーチンの最も典型的な例の 1 つは、生産者-消費者問題です。値を継続的に生成する関数(例: ファイルから読み取る)と、継続的にこれらの値を消費する関数(例: 他のファイルに書き込む)があるものとします。通常、これらの 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 を一致させる方法です。これは、メインループを処理する問題は通常の場合です。生産者と消費者の両方がアクティブであり、それぞれ独自のメインループを持ち、それぞれが他方が呼び出し可能なサービスであると想定しています。この特定の例では、関数の 1 つの構造を変更し、そのループを展開してパッシブエージェントにすることは容易です。ただし、この構造の変更は、他の実際のシナリオではそれほど容易ではない場合があります。

コルーチンは、生産者と消費者を照合するための理想的なツールを提供します。これは、レジューム-降伏のペアが呼び出し側と被呼び出し側の典型的な関係を上下逆に変えるためです。コルーチンが降伏を呼び出す場合、新しい関数に入力されません。代わりに、保留中の呼び出し(レジュームへ)を返します。同様に、レジュームの呼び出しは新しい関数を開始するのではなく、降伏への呼び出しを返します。このプロパティは、それぞれがマスターのように動作し、他方がスレーブであるかのように、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 パイプのことを考えた場合、あなただけではありません。結局のところ、コルーチンは(非プリエンプティブな)マルチスレッドの一種です。パイプでは各タスクが別々のプロセスで実行されるのに対し、コルーチンでは各タスクが別々のコルーチンで実行されます。パイプは、書き手(生産者)と読み手(消費者)の間にバッファを提供するため、相対速度に多少の自由度があります。これは、プロセス間で切り替えるコストが高いため、パイプのコンテキストでは重要です。コルーチンでは、タスク間の切り替えコストははるかに低くなります(関数呼び出しと同程度のコスト)。そのため、書き手と読み手は追従できます。