「3日目」の章、の3日目。今回で「7つの言語 7つの世界 Ioの章」のラスト。

- 作者: Bruce A. Tate,まつもとゆきひろ,田和勝
- 出版社/メーカー: オーム社
- 発売日: 2011/07/23
- メディア: 単行本(ソフトカバー)
- 購入: 9人 クリック: 230回
- この商品を含むブログ (64件) を見る
コルーティン
Ioで非同期処理をするばあい、コルーティンを使います。
まず、普通の処理の場合。
say := method( for(i, 1, 3, writeln("[", name, "] ", i)) ) sub1 := Object clone do(name := "sub1") sub2 := Object clone do(name := "sub2") sub3 := Object clone do(name := "sub3") name := "main" sub1 say sub2 say sub3 say say
実行結果。
$ io coro1.io [sub1] 1 [sub1] 2 [sub1] 3 [sub2] 1 [sub2] 2 [sub2] 3 [sub3] 1 [sub3] 2 [sub3] 3 [main] 1 [main] 2 [main] 3
普通に順番に処理されます。
自分の処理を止めて別のコルーティンに実行権をわたすにはyield
を使います。
上のスクリプトにyield
を入れてみます。
say := method( for(i, 1, 3, writeln("[", name, "] ", i) yield ) ) sub1 := Object clone do(name := "sub1") sub2 := Object clone do(name := "sub1") sub3 := Object clone do(name := "sub1") name := "main" sub1 say sub2 say sub3 say say
実行結果…は変わらないので省略。実のところ各オブジェクトが別々のコルーティンで動作している必要があるのでyield
を挿入しただけでは非同期に動作しません。
各オブジェクトを非同期に動作させるためにはメッセージを非同期メッセージとして送ります。そのばあい、メッセージの前に@
または@@
をつけます。ふたつの違いは値を返すか返さない(nil
を返す)かの違いで、動作は同じです。
実例。
say := method( for(i, 1, 3, writeln("[", name, "] ", i) yield ) ) sub1 := Object clone do(name := "sub1") sub2 := Object clone do(name := "sub2") sub3 := Object clone do(name := "sub3") name := "main" sub1 @@say sub2 @@say sub3 @@say say
実行結果
[main] 1 [sub3] 1 [sub2] 1 [sub1] 1 [main] 2 [sub3] 2 [sub2] 2 [sub1] 2 [main] 3 [sub3] 3 [sub2] 3 [sub1] 3
各々のオブジェクトでyield
が実行されるたびに次のオブジェクトに実行権が移り、順に動作しているのがわかります。
オブジェクトが実行待ちしている様子も表示してみます。
say := method( for(i, 1, 3, writeln("[", name, "] ", i, " : yielding ", Scheduler yieldingCoros map(runTarget name)) yield ) ) sub1 := Object clone do(name := "sub1") sub2 := Object clone do(name := "sub2") sub3 := Object clone do(name := "sub3") name := "main" sub1 @@say writeln("yielding ", Scheduler yieldingCoros map(runTarget name)) sub2 @@say writeln("yielding ", Scheduler yieldingCoros map(runTarget name)) sub3 @@say writeln("yielding ", Scheduler yieldingCoros map(runTarget name)) say
実行結果。
yielding list(sub1) yielding list(sub2, sub1) yielding list(sub3, sub2, sub1) [main] 1 : yielding list(sub3, sub2, sub1) [sub3] 1 : yielding list(sub2, sub1, main) [sub2] 1 : yielding list(sub1, main, sub3) [sub1] 1 : yielding list(main, sub3, sub2) [main] 2 : yielding list(sub3, sub2, sub1) [sub3] 2 : yielding list(sub2, sub1, main) [sub2] 2 : yielding list(sub1, main, sub3) [sub1] 2 : yielding list(main, sub3, sub2) [main] 3 : yielding list(sub3, sub2, sub1) [sub3] 3 : yielding list(sub2, sub1, main) [sub2] 3 : yielding list(sub1, main, sub3) [sub1] 3 : yielding list(main, sub3, sub2)
現在実行中のオブジェクトがyield
すると、キューの先頭のオブジェクトが実行に移り、yield
したオブジェクトがキューの末尾に移動するのがわかります。
ひとつ謎なところ。
ガイドには次のように書かれています。
The Scheduler object is responsible for resuming coroutines that are yielding. The current scheduling system uses a simple first-in-first-out policy with no priorities.
ですが、最後に非同期メッセージを送ったオブジェクトがキューの先頭に挿入されています。Ioのソースコードの該当部分を見てみても、たしかに先頭に挿入しています。末尾に追加するコードもコメントとして残っているんですが、方針の変更かなにかがあったんでしょうか。
アクター
コルーティンの説明をしてしまうと、独立してアクターについて説明することは実のところ少なくて。Ioにおいては非同期メッセージを受け取るオブジェクトはアクターになる、というシンプルなことのようです。
one := Object clone two := Object clone three := Object clone one say := method(wait(1); writeln("1, ")) two say := method(wait(2); writeln("2, ")) three say := method(wait(3); writeln("3, ")) three @@say two @@say one @@say wait(4) writeln("Vitoria!")
実行結果。
$ io actor.io 1, 2, 3, Vitoria!
実行すると1秒おきに「1, 」「2, 」「3, 」「Vitoria!」と表示されます。非同期メッセージを送った順序に関係なく、各々のオブジェクトが各々にメッセージを処理するためです。
フューチャ
実行権を受け渡しつつ動作するというのは、メインのプロセッサだけを使うばあいには、あまり恩恵を感じないのですが、プロセッサを使わない待ち状態が長い処理に使うと、待ち時間を有効に使うことができるようになります。
HugeFile := Object clone HugeFile readContents := method( writeln("\treading huge file...") wait(5) writeln("\t...done") "huge contents" ) hugeFile := HugeFile clone contents := hugeFile @readContents writeln("Huge file's contents is ...") writeln(contents)
実行結果。
$ io future1.io Huge file's contents is ... reading huge file... (ここで5秒停止) ...done huge contents
巨大ファイルを読み込むばあいを考えます。
単純にcontents := hugeFile readContents
とすると、contents
に値が入るまですべての処理がブロックしてしまいます。
そこでcontents := hugeFile @readContents
と非同期で処理されるようにします。contents
には必要な値がまだ入っていませんが、すぐに処理が戻り次の行のwriteln("Huge file's contents is ...")
が実行されます。
次にwriteln(contents)
でcontents
にアクセスすると、contents
の値はまだ有効でないので動作がブロックされhugeFile
に処理が移り読み込みが始まります。
読み込みが終了するとcontents
の値が有効になりwriteln
によって表示されます。
正直、これでもまだ利点がわかりにくいかも。
さらに手を加えて。
HugeFile := Object clone HugeFile readContents := method( writeln("\treading huge file...") wait(5) writeln("\t...done") "huge contents" ) hugeFile := HugeFile clone contents := hugeFile @readContents writeln("Huge file's contents is ...") wait(1) writeln("... do something ...") writeln(contents)
実行結果。
$ io future2.io Huge file's contents is ... reading huge file... ... do something ... ...done huge contents
hugeFile
にメッセージを送ったあとに、処理をブロックするようななにかを実行したと考えてください(ここでは処理がブロックされたのをあらわすためにwait(1)
を挿入してます)。これで実行権がhugeFile
に移り、「reading huge file...」を表示しています。
hugeFile
もwait(5)
で処理が停止すると、実行権が戻りwait(1)
のブロックから抜けて「... do something ...」を表示します。次にcontents
にアクセスすることで再び処理がブロックます。「...done」を表示してreadContents
の処理が終わるとcontents
の値が有効になり、その値を表示して終了します。
もっと現実的な例として本書にもあるHTTPリクエストの例。
googleSite := Object clone googleSite readContents := method( url := "http://www.google.com/" writeln("\tfetch \"", url, "\"") contents := URL with(url) fetch writeln("\tfetched") contents ) contents := googleSite @readContents writeln("read Google site") wait(0.1) writeln("...do something...") writeln(contents sizeInBytes, " bytes read") writeln("done")
実行結果。
$ io future3.io read Google site fetch "http://www.google.com/" ...do something... fetched 15412 bytes read
Futureについては、結城浩先生の書籍「増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編」に詳しい説明があります。とてもわかりやすく丁寧な説明がされていますので、関心のある方は一読をお勧めします。
「Futureパターン」はサイトでも紹介されています。
ちがった。
結城先生のサイトの書籍紹介ページ

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/03/21
- メディア: 大型本
- 購入: 15人 クリック: 287回
- この商品を含むブログ (204件) を見る
いつか読むはずっと読まない:どっちを向いても未来、どこまで行っても未来
買い忘れてた。買ってこなきゃ。

- 作者: 鶴田謙二
- 出版社/メーカー: 東京創元社
- 発売日: 2011/11/29
- メディア: 大型本
- 購入: 1人 クリック: 13回
- この商品を含むブログ (11件) を見る