English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Lua 協同プログラム(coroutine)

コルーチンとは何ですか?

Luaのコルーチンはスレッドと比較して似ています:独立したスタック、独立したローカル変数、独立したコマンドポインタを持ちつつ、他のコルーチンとともにグローバル変数やほとんどすべてのものを共有しています。

コルーチンは非常に強力な機能ですが、使い方は複雑です。

スレッドとコルーチンの違い

スレッドとコルーチンの主な違いは、複数のスレッドを持つプログラムが同時に複数のスレッドを実行できるのに対し、コルーチンはお互いに協力して実行する必要があります。

指定された任意の瞬間に、コルーチンが1つだけ実行されます。そして、実行中のコルーチンは、明示的にスレイプ要求されたときだけスレイプされます。

コルーチンは、同期多スレッドに少し似たもので、同じスレッドロックを待つ複数のスレッドはコルーチンに少し似ています。

基本的な構文

メソッド説明
coroutine.create()コルーチンを作成し、コルーチンを返します。引数は関数であり、resumeと組み合わせて使用すると、関数の呼び出しを再開します。
coroutine.resume()コルーチンを再開します。createと組み合わせて使用します。
coroutine.yield()コルーチンをスレイプします。コルーチンをスレイプ状態に設定し、resumeと組み合わせて使用すると、多くの効果があります。
coroutine.status()コルーチンの状態を確認します
注:コルーチンの状態は3種類あります:dead、suspended、running。どのタイミングでこのような状態になるかは以下のプログラムを参照してください。
coroutine.wrap()コルーチンを作成し、関数を返します。この関数を呼び出すと、コルーチンに移行します。create機能と重複しています。
coroutine.running()現在実行中のコルーチンを返します。コルーチンはスレッドと同じであり、runningを使用すると、コルーチンのスレッドIDを返します。

以下の例では、上記の各メソッドの使用法を示しています:

-- coroutine_test.lua ファイル
co = coroutine.create(
    function(i)
        print(i);
    end
)
 
coroutine.resume(co, 1)   -- 1
print(coroutine.status(co))  -- dead
 
print("----------")
 
co = coroutine.wrap(
    function(i)
        print(i);
    end
)
 
co(1)
 
print("----------")
 
co2 = coroutine.create(
    function()
        for i=1,10 do
            print(i)
            if i == 3 then
                print(coroutine.status(co2))  --running
                print(coroutine.running()) --thread:XXXXXX
            end
            coroutine.yield()
        end
    end
)
 
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
 
print(coroutine.status(co2))   -- suspended
print(coroutine.running())
 
print("----------")

以下の例の実行結果は:

1
dead
----------
1
----------
1
2
3
running
thread: 0x7fb801c05868    false
suspended
thread: 0x7fb801c04c88    true
----------

coroutine.runningを使うと、coroutineが底層でスレッドとして実装されていることがわかります。

coroutineをcreateするとき、新しいスレッドにイベントを登録することになります。

resumeを使用してイベントをトリガーするとき、createのcoroutine関数が実行されます、yieldに遭遇すると現在のスレッドを一時停止し、次のresumeのトリガーを待ちます。

次に、より詳細な例を分析します:

function foo (a)
    print("foo 関数の出力", a)
    return coroutine.yield(2 * a) -- 返します  2*a の値
end
 
co = coroutine.create(function (a , b)
    print("第1回コルーチン実行の結果出力", a, b) -- co-body 1 10
    local r = foo(a + 1)
     
    print("第2回コルーチン実行の結果出力", r)
    local r, s = coroutine.yield(a + b, a - b)  -- a、bの値は第1回コルーチン呼び出し時に入力されます
     
    print("第3回コルーチン実行の結果出力", r, s)
    return b, "コルーチンを終了します"                   -- bの値は第2回コルーチン呼び出し時に入力されます
end)
        
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割線----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割線---")

以下の例の実行結果は:

第1回コルーチン実行の結果出力    1    10
foo 関数の出力    2
main    true    4
--分割線----
第2回コルーチン実行の結果出力    r
main    true    11    -9
---分割線---
第3回コルーチン実行の結果出力    x    y
main    true    10    コルーチンを終了します
---分割線---
main    false    cannot resume dead coroutine
---分割線---

以下の例を続けます:

  • resumeを呼び出し、コルーチンを唤醒します、resume操作が成功するとtrueが返され、それ以外の場合はfalseが返されます;

  • コルーチンが実行されます;

  • yield文まで実行されます;

  • yieldがコルーチンを一時停止させ、最初のresumeが返されます;(注意:ここでのyieldはresumeの引数を返します)

  • 第二次のresume、コルーチンを再び唤醒します;(注意:ここでのresumeの引数には、最初の引数以外の引数がyieldの引数として使用されます)

  • yieldが返信されます;

  • コルーチンが再び実行されます;

  • 使用しているコルーチンが再び実行されるとき、resumeメソッドが呼び出されると、cannot resume dead coroutineとエラーが表示されます。

resumeとyieldの組み合わせが強力なのは、resumeがメインプロセスに位置し、外部の状態(データ)をコルーチン内部に传入し、一方でyieldが内部の状態(データ)をメインプロセスに返すからです。

プロデューサー-消費者問題

今、Luaの協同プログラムを使用して、プロデューサー問題を解決します。-消費者問題という古典的な問題です。

local newProductor
function productor()
     local i = 0
     while true do
          i = i + 1
          send(i)     -- 生産されたアイテムを消費者に送信します
     end
end
function consumer()
     while true do
          local i = receive()     -- プロデューサーからアイテムを取得します
          print(i)
     end
end
function receive()
     local status, value = coroutine.resume(newProductor)
     return value
end
function send(x)
     coroutine.yield(x)     -- xは送信する必要がある値を示し、値が返信された後、このコルーチンは停止します
end
-- プログラムを起動します
newProductor = coroutine.create(productor)
consumer()

以下の例の実行結果は:

1
2
3
4
5
6
7
8
9
10
11
12
13
……