モダンPerlの世界へようこそ

第13回 AnyEvent:イベント駆動モジュールの方言を吸収する

この記事を読むのに必要な時間:およそ 4.5 分

タイマーを追加してみよう

もちろんこれだけではただ my $input = <STDIN>; するのと変わりません。このスクリプトにもうひとつタイマーを追加してみましょう。Windows環境では意図したのとは異なる動作になってしまうかもしれませんが,これで標準入力に値を入れるまでタイマーの時刻が表示され続けるようになります。

@@ -15,6 +15,18 @@
     },
 );
 
+my $cv_timer = AnyEvent->condvar;
+my $timer; $timer = AnyEvent->timer(
+    after    => 0,
+    interval => 1,
+    cb       => sub {
+        print AnyEvent->time, "\n";
+        $cv_timer->send;
+    },
+);
+$cv_timer->recv;
+
 if (defined(my $input = $cv->recv)) {
     print "got: [$input]\n";
 }
+undef $timer;

イベントループの種類を変えてみよう

AnyEventの(本来の)キモは,イベントループの種類によらずこのスクリプトを実行できることです。いまは移植性を優先してPure Perlのループを使いましたが,今度は use AnyEvent::Impl::Perl; の行をコメントアウトしてみてください。AnyEventは(あれば)EV,あるいはEventを使ってループを処理してくれます(どちらも入っていなければ,結局Pure Perlのループになります⁠⁠。

EVやEventでの処理を確認できたら,POEでも動くか試してみましょう。これも例によってOSの種類やPOEのバージョン,AnyEventのバージョンなどによってエラーが出る可能性がありますが,AnyEventの特徴のひとつはつかめるのではないかと思います。

@@ -1,8 +1,9 @@
 #!perl
 use strict;
 use warnings;
-use AnyEvent::Impl::Perl;
+#use AnyEvent::Impl::Perl;
 use AnyEvent;
+use POE;
 
 my $cv = AnyEvent->condvar;
 my $io; $io = AnyEvent->io(

CoroとAnyEvent,EVを使った競馬ゲーム

さて,今度はCoroを加えて,簡単な競馬ゲームを実装してみます。async {}; の中身がCoroによるコルーチンで,その裏では(暗黙のうちにAnyEventが呼び出している)EVのループが回っています。コルーチンからメインループに処理を戻すときには cede; を,コルーチンの終了時には(AnyEventのイベントを止めるために)$cv->send を呼んで結果を順位保持用の配列に渡しているほか,念のためCoro::Handleを使って,イベントが回っている間の出力はブロックさせないようにしてみました(もちろんこの文脈なら単にprintするだけでも十分ですが⁠⁠。

#!perl
use strict;
use warnings;
use EV;
use Coro;
use Coro::AnyEvent;
use Coro::Handle;

my @cvs;
my $out = unblock \*STDOUT;
my $place = 0;
foreach my $i (1..10) {
    $cvs[$i] = AnyEvent->condvar;
    async {
        my $count = 0;
        until ($count > 10) {
            Coro::AnyEvent::sleep rand(1);
            $out->syswrite("ponie $i => $count\n");
            $count++;
            cede;
        };
        $cvs[$i]->send(++$place);
    };
}

my @places;
foreach my $i (1..10) {
    $places[ $cvs[$i]->recv ] = $i;
}

print "----------------\n";
foreach my $i (1..10) {
    print "$i => ponie $places[$i]\n";
}

今回のサンプルでは単に適当な時間,処理待ちをしているだけですが,もちろん実際のアプリケーションなら,コルーチンのなかにはウェブページを取ってくるコードなどが入ることでしょう。

今回は最初からCoro+AnyEvent前提でコードを用意しましたが,既存のアプリケーションでも,イベントループを呼ぶ前にCoroとCoro::AnyEventをロードして,非同期化したいところを async {}; ないし async_pool {}; でくくり,適当なところに cede; を挟んでやれば,基本的なCoro+AnyEventへの移行はおしまい。もちろん実際にはそれ以外にもさまざまな点で微調整が必要になってきますが,アプリケーションを全面的にイベント対応させることを思えば,このCoro+AnyEvent(+EV)の組み合わせは非常にコストパフォーマンスの高い改良になる「かも」しれません。

AnyEventは成功するのか?

Any系のモジュールが成功するかどうかはふつう,ほかの関連モジュールの開発者をいかにうまく取り込めるかにかかっています。

いかに理念がすばらしいものであっても,ほかの開発者に協力してもらえなければ,結局人は自分の慣れたイベントモジュールを使い続けるでしょうし,そのモジュール専用の拡張モジュールを書き続けてしまうものです。

ましてこの分野ではPOEという巨人がいて,たいていのことはすでにPOEの拡張モジュールが用意されています。AnyEvent用に調整したモジュールをリリースすることは単なる車輪の再発明にしかならない可能性も少なくありません。

そして,残念ながら「どんなイベントモジュールが相手でも使える関連モジュールを用意する」という文脈でのAnyEventの将来性は,決して明るいとはいえなさそうです。

前述の通り,いまの時点でAnyEventを積極的にサポートしていこうというコミュニティはほとんど見あたりません。AnyEventのドキュメント(とりわけAnyEvent::Impl::以下)には既存のモジュールやコミュニティに対する不平不満が大量に見られますから,既存のコミュニティからの建設的な協力を期待する方が野暮でしょう。とりわけPOEコミュニティとはAnyEvent::Impl::POEの実装やその結果生じたベンチマークの結果をめぐって鋭く対立してきた経緯があり,当面,両者が協力して事態の解決にあたることにはなりそうにありません※3⁠。

※3

しかも,最近はAnyEventに限らずAny系のモジュール全般に対して,いくら表面的なところを取り繕ってみたところで,環境(とりわけロードされているモジュール)によって動作が変わったり不安定になるようでは安心して使えない(それよりはそれぞれのバックエンドにあわせたモジュールを使うほうがいい⁠⁠,という批判が強くなってきているため,既存のイベント関連モジュールの作者がAnyEventに移行する理由はなくなってきています。

もっとも,既存のイベント駆動アプリケーションを読みやすくするためにコルーチンを追加したい,とか,ほかのイベントモジュールとの連携はおまけと考えて,Coro+AnyEvent+EVというレーマンウェアだけあればいい,という人がどのくらい増えるかはまた別の話ですし,こちらについてはまだまだ判断できるような状態にはありません。

HTTP::EngineRemedieのようにAnyEventへの対応を始めた国産モジュール/アプリケーションが今後どのような判断を下していくのかを含めて,日本での実験は海外からも注目されているようです。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/