空前の非同期ブームが襲来した 2009 年でしたけれども、ぼくは Coro をほぼスルー、後で勉強しよう後で勉強しようとか思って結局勉強しなかったクソでした。このままではマズい。死ぬ。そういうわけで、新幹線の中でちょっと Perldoc 読んでた。
何がよくわかってなかったかってその筆頭にあったのが AnyEvent と Coro の関係であって、え、なんなの、どういうことなの、って感じていたのだけども、要するに Coro が適切なタイミングで AnyEvent のイベントループを回してくれて嬉しいね、ということじゃねーかという理解をした。
Coro を使って async を呼ぶと、Coroutine が生成されて Ready Queue に入る。Coro で生成されるのは協調スレッドであるから、明示的に CPU 資源を譲渡しない限り、協調スレッド側に制御が移らない。制御を移す役割を果たすのが cede あるいは schedule である。
async { print "async 1\n"; }; print "main 1\n"; cede; # ここで制御が Coroutine に移る print "main 2\n"; # prints # main 1 # async 1 # main 2
cede や schedule は、現在動いているスレッド(cede or schedule を呼び出すスレッド; $Coro::current)から、Ready Queue に並んでいるスレッドの一つに制御を移す。
cede と schedule の違いは、現在動いているスレッドを Ready Queue に入れるか入れないかというところにある。schedule は現行スレッドを Ready Queue に入れないので、上記の cede を schedule に置換すると以下のようなメッセージを出力後、die する。
# prints # main 1 # async 1 # FATAL: deadlock detected at ./coro.pl line 0
なぜ die するかというと、Ready Queue が空になり、次に動かすべきスレッドが見つからなくなったためである。schedule では現行スレッドを Ready Queue に入れないため、async で生成した Coroutine が実行された後(Ready Queue から出て行った後)、実行すべき Coroutine がなくなる。Ready Queue が空の状態だと、$Coro::idle が呼ばれ、この中で die するようになっている。
# in Coro.pm $idle = sub { require Carp; Carp::croak ("FATAL: deadlock detected"); };
単純に Coro を使うだけならこれで良いのだけれど、イベンドドリブンなプログラムを書く場合は Ready Queue が空になるケースというのは良くある。このときに一々 die されていたのではかなわない。そこで Coro::AnyEvent ではこの $Coro::idle を書き換えて、schedule を呼ぶ。
$IDLE = new Coro sub { my $one_event = AnyEvent->can ("one_event"); while () { $one_event->(); Coro::schedule; } };
これによって、反応すべきイベントがある場合はそれに反応する Coroutine に切り替わり、そうでないときは待機というアクションが可能になっている、のだと思う。