1. ホーム
  2. スクリプト・コラム
  3. ルア

Luaコルーチンプログラムの実行時解析

2022-02-11 17:59:35

から、luaの並列プログラム(コルーチン)のコード解析です。 Luaリファレンスマニュアルインターフェイス (若干の修正あり)。

コピーコード コードは以下の通りです。

function foo (a)
    print("foo", a)
    return coroutine.yield(2*a)
end

co = coroutine.create(function (a,b)
   print("co-body1", a, b)
   local r = foo(a+1)
   print("co-body2", r)
   local r, s = coroutine.yield(a+b, a-b)
   print("co-body3", r, s)
   return b, "end"
end)

print("1----")
print("main", coroutine.resume(co, 1, 10))
print("2----")
print("main", coroutine.resume(co, "r"))
print("3----")
print("main", coroutine.resume(co, "x", "y"))
print("4----")
print("main", coroutine.resume(co, "x", "y"))


以下のように実行されます。
コピーコード コードは以下の通りです。

1------
co-body1 1 10
foo 2
main true 4
2------
co-body2 r
main true 11 -9
3------
co-body3 x y
main true 10 end
4------
main false cannot resume dead coroutine

ここでは、4つの再開の呼び出しがあります。

1回目

コピーコード コードは以下の通りです。

print("main", coroutine.resume(co, 1, 10))

1. aとbの値をレジュメのa=1, b=10としてprint("co-body1", a, b)を実行します。
2. foo(a)にa+1=2を計算し、引数aに先ほどの計算結果を渡しながら、print("foo", a)を実行します。
3. return coroutine.yield(2*a) を検討する。
4. 2*a=4 を計算し、yield を押して foo(a) 呼び出しを停止し、4 を返して再開します。fooのreturnはまだ実行されていないことに注意。
5. resumeが正常に実行され、trueを返す, 4.

2回目

コピーコード コードは以下の通りです。

print("main", coroutine.resume(co, "r"))

1. 最後の保留中のfoo(a)呼び出しから実行を開始し、その後に未完了の戻り値呼び出しが続く。
2. yield は resume の呼び出し引数を返すので、foo(a+1) が返す値は "r" という文字列になります。これは理解しにくいですね。
なぜなら、変数local rの値はyield(2*a)の2*aの値であるべきだと考えるのが論理的だからです。
yieldの戻り値は、yieldの引数の値と同じではないことに注意してください。
前者は変数に保存したり、返したり、(yieldの結果を保存せずに)そのままにしておくことができ、後者はresumeの戻り値である。
3. 3. print("co-body2", r)を実行すると、rの値は "r" となる。
4. 4. ローカル r, s = coroutine.yield(a+b, a-b) を考える。
5. 5. a+b=11、a-b=-9を計算し、yieldを押してcoコールをハングアップし、11と9を返して再開する。なお、ローカルr,sの代入はまだ始まっていない。
ここではっきりしないのは、なぜaの値が"r"でないのか、ということです。なぜなら、"r" はすでに上の yield の戻り値で消費されているからです。
6. resume が正常に実行され、true, 11, -9 が返される。

3回目

コピーコード コードは以下の通りです。

print("main", coroutine.resume(co, "x", "y"))

1. 最後のyieldがあったところから実行し、その後に完了しなかったローカルr, s =代入を実行する。前述の通り、yieldはレジューム呼び出しのパラメータを返すので、rとsの値は"x"と"y"となる。
2. print("co-body3", r, s)を実行し、印刷します。
3. return b, "end"を考える。
4. bの値はずっと変わらず10であり、ここでは文字列"end"と共に直接返されています。
5. 同時進行の関数が戻ると、その戻り値はすべてレジュームの戻り値として返されます。つまり、ここでのレジュームは成功し、10、"end"を返します。

4回目

コピーコード コードは以下の通りです。

print("main", coroutine.resume(co, "x", "y"))

co関数がリターンしたため、デッド状態になっており、レジュームできないので、4回目のレジュームに失敗しています。