初版は Lua 5.0 向けに書かれました。後のバージョンにも大部分で関連していますが、違いがいくつかあります。
第 4 版は Lua 5.3 を対象としており、Amazon や他の書店で入手できます。
この本を購入することで、Lua プロジェクト のサポートにも協力できます。
![]() |
Lua によるプログラミング | ![]() |
パート I. 言語 チャプター 9. コルーチン |
コルーチンの最も典型的な例の 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(その実装では、生産者も消費者も永遠に実行されます。処理されるべきデータがなくなったら停止するように変更することは容易な作業です。)ここでの問題は、
send
と receive
を一致させる方法です。これは、メインループを処理する問題は通常の場合です。生産者と消費者の両方がアクティブであり、それぞれ独自のメインループを持ち、それぞれが他方が呼び出し可能なサービスであると想定しています。この特定の例では、関数の 1 つの構造を変更し、そのループを展開してパッシブエージェントにすることは容易です。ただし、この構造の変更は、他の実際のシナリオではそれほど容易ではない場合があります。コルーチンは、生産者と消費者を照合するための理想的なツールを提供します。これは、レジューム-降伏のペアが呼び出し側と被呼び出し側の典型的な関係を上下逆に変えるためです。コルーチンが降伏
を呼び出す場合、新しい関数に入力されません。代わりに、保留中の呼び出し(レジューム
へ)を返します。同様に、レジューム
の呼び出しは新しい関数を開始するのではなく、降伏
への呼び出しを返します。このプロパティは、それぞれがマスターのように動作し、他方がスレーブであるかのように、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 パイプのことを考えた場合、あなただけではありません。結局のところ、コルーチンは(非プリエンプティブな)マルチスレッドの一種です。パイプでは各タスクが別々のプロセスで実行されるのに対し、コルーチンでは各タスクが別々のコルーチンで実行されます。パイプは、書き手(生産者)と読み手(消費者)の間にバッファを提供するため、相対速度に多少の自由度があります。これは、プロセス間で切り替えるコストが高いため、パイプのコンテキストでは重要です。コルーチンでは、タスク間の切り替えコストははるかに低くなります(関数呼び出しと同程度のコスト)。そのため、書き手と読み手は追従できます。
Copyright © 2003–2004 Roberto Ierusalimschy。全著作権所有。 | ![]() |