読者です 読者をやめる 読者になる 読者になる

北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

AnyEventとCoro::AnyEventで諸注意みたいなの

またまた非同期ブームに乗ってお勉強継続中です。

AnyEventとCoro::AnyEventの違い

AnyEventとCoro::AnyEventでは、メインループを回すスレッドが違います。

use strict;
use warnings;
use AnyEvent;
use Coro;
# use Coro::AnyEvent;

my $done = AE::cv;

my $t = AE::timer 0, 0, sub {
	print $Coro::current->{desc}, "\n";
	$done->send;
};

$done->recv;

このまま動かすと出力は「[main::]」で、Coro::AnyEventを有効にすると「[AnyEvent idle process]」となります。

裏でメインループが回る

Coro::AnyEventを有効にすると idle 状態でループが回るようになるので、 recv しなくてもループを回すことができます。具体的には、readyなスレッドがないときにscheduleすればOKです。 Coro::* シリーズが明示的にAnyEventのメインループを回さなくてもうまく動いているのは、この原理を利用してCoroのidleプロセスが裏でメインループを回しているからです。

以下の例では、condvarの代わりにメインスレッドのCoroを終了のフラグに使っています*1。condvarを使ったときとほぼ似たようなコードですね。

【9/24追記】readyは別スレッドから勝手に呼べるので、$done などのフラグを用意して schdule while !$done; とかの方がよりよいと思います。

use strict;
use warnings;
use AnyEvent;
use Coro;
use Coro::AnyEvent;

my $main = $Coro::current;

my $t = AE::timer 0, 0, sub {
	print $Coro::current->{desc}, "\n";
	$main->ready;
};

schedule;

メインループをブロックしない

昨日のエントリでrouse_waitの使い方を書きましたが、メインループを回しているスレッド([AnyEvent idle process])からrouse_waitを呼ぶと不幸なことになります*2。rouse_waitでメインループが進むことを期待しているのに、メインループをブロックしているのは自分だからです。

schedule に関しても同様です。メインループが $Coro::idle ですので、readyなスレッドがないと自己循環な呼び出しになってしまいます*3

メインループとメインスレッドは違う

ちょっとややこしいのは、Coro::AnyEventの環境だと「メインループを回しているスレッドとメインスレッドが違う」ということです。言葉の使い方を気をつけないと、情報交換に支障が出たり変なとこでハマったりしそうです。

*1:この書き方が正当なのかhack的なのかはわかりませんが

*2:この辺りはmalaさんからのツッコミコメントを受けて気がつきました。

*3:この辺の挙動は、ver. 5.17現在でも、エラーにならずブロッキングもなぜかしなかったりと、かなり怪しい。