From: Yusuke ENDOH Date: 2007-06-13T19:31:30+09:00 Subject: [ruby-dev:30969] Re: Supporting Fiber 遠藤です。 長文失礼します。Lua と Modula-2 をちょっとだけ試してみたので報告します。 はじめての Lua と Modula-2 なので間違いがあったらごめんなさい。 検証したコードは末尾に載せます。 Lua の coroutine は semi-coroutine で、coroutine 間に親子関係がありました。 実行を移すコマンドが二種類 (resume と yield) あります。 resume で他の coroutine を駆動し、その coroutine が yield するのを待ちます。 resume は親から子を、yield は子から親を駆動する感じです (親は子を選べますが、 子は親を選べません) 。 coroutine が終了すると、その coroutine が yield したのと同じ動作をしました。 親子関係を壊すような遷移 (子から親を resume する) はエラーになりました。 終了した coroutine を resume してもエラーでした。 Modula-2 の coroutine は純粋 (?) な coroutine で、coroutine 同士は対等です。 従って実行を移すコマンドは一種類 (TRANSFER) だけです。 TRANSFER で他の coroutine を駆動します。TRANSFER した coroutine は他の coroutine から TRANSFER されるまで絶対に動きません。 coroutine が終了するとプログラムごと終了しました。 Lua 式と Modula-2 式のどちらがいいかは一長一短だと思います。 1.9 の Fiber は Lua と Modula-2 の中間 (やや Modula-2 寄り) にいる感じ でしょうか。coroutine 終了時に yield するのは Lua っぽいですが、 実行を移すのが 1 種類だけのは Modula-2 っぽいです。 Lua は resume と yield の区別によって coroutine の親子関係が保証されており、 coroutine の終了時の挙動 (親に戻る) が関数呼び出しっぽくてわかりやすいです。 Modula-2 は親も子もなく実行はただ一本で、それが途切れたら全部終わり、という、 乱暴な挙動ではありますが、これはこれで筋が通っていると思います。 1.9 の現状の Fiber はこのあたりが中途半端なので prev が変なところを 指している感じがします。 あと噂では、Simula は Lua 式と Modula-2 式の両方の機能を提供しているらしい (しかしそのせいで非常に複雑な実装になっているらしい) です。 非常にわかりにくいメールですみません。 以下、検証したコードです。※の数字の順に実行されます。 [[test.lua ここから]] c1 = coroutine.create(function () print("1") -- ※ 3 ※ coroutine.yield() -- ※ 4 ※ print("2") -- ※ 到達しない ※ end) c2 = coroutine.create(function () coroutine.resume(c1) -- ※ 2 ※ -- ※ 5 ※ end) coroutine.resume(c2) -- ※ 1 ※ -- ※ 6 ※ -- ※ ここで実行終了 ※ [[test.lua ここまで]] > $ lua test.lua > 1 [[test.mi ここから]] MODULE test; FROM SYSTEM IMPORT WORD, ADR, ADDRESS, TRANSFER, NEWPROCESS; FROM InOut IMPORT WriteInt, WriteLn; VAR main, co1, co2 : ADDRESS; Workspace1 : ARRAY [0..1023] OF WORD; Workspace2 : ARRAY [0..1023] OF WORD; PROCEDURE CO1; BEGIN WriteInt (1, 1); WriteLn; (* ※ 4 ※ *) TRANSFER (co1, co2); (* co2.yield 相当 *) (* ※ 5 ※ *) WriteInt (2, 1); WriteLn; (* 到達しない *) END CO1; PROCEDURE CO2; BEGIN TRANSFER (co2, co1); (* co1.yield 相当 *) (* ※ 3 ※ *) (* ※ 6 ※ *) (* ここで実行終了 *) END CO2; BEGIN (* ※ 1 ※ *) (* co1 = Fiber.new { CO1() }; co2 = Fiber.new { CO2() } *) NEWPROCESS (CO1, ADR (Workspace1), SIZE (Workspace1), co1); NEWPROCESS (CO2, ADR (Workspace2), SIZE (Workspace2), co2); TRANSFER (main, co2); (* co2.yield 相当 *) (* ※ 2 ※ *) (* 到達しない *) END test. [[test.mi ここまで]] > $ mocka -c test && mocka -p test && ./test > 1 Modula-2 は mocka という実装で試してみました。 http://www.info.uni-karlsruhe.de/~modula/index.php Lua のエラーのテスト: [[test2.lua ここから]] c1 = coroutine.create(function () print(coroutine.resume(c2)) -- 親を resume しようとする end) c2 = coroutine.create(function () coroutine.resume(c1) end) coroutine.resume(c2) print(coroutine.resume(c2)) -- 死んだ coroutine を resume しようとする [[test2.lua ここまで]] > $ lua test2.lua > false cannot resume non-suspended coroutine > false cannot resume dead coroutine Lua は coroutine の終了で親に帰っていく: [[test3.lua]] f1 = coroutine.create(function () print("f1 before") coroutine.resume(f2) print("f1 after") end) f2 = coroutine.create(function () print("f2 before") coroutine.resume(f3) print("f2 after") end) f3 = coroutine.create(function () print("f3 before") coroutine.yield() print("f3 after") end) coroutine.resume(f1) [[test3.lua ここまで]] > $ lua test3.lua > f1 before > f2 before > f3 before > f2 after > f1 after 上と似ている Ruby のコード [[test.rb ここから]] f2 = f3 = nil f1 = Fiber.new do puts "f1 before" f2.yield puts "f1 after" end f2 = Fiber.new do puts "f2 before" f3.yield puts "f2 after" end f3 = Fiber.new do puts "f3 before" f2.yield puts "f3 after" end f1.yield [[test.rb ここまで]] > $ ./ruby test.rb > f1 before > f2 before > f3 before > f2 after > f3 after -- Yusuke ENDOH