AnyEventを使った非同期プログラミング


最近非同期プログラミング周辺はなぜか絡むことが多いです。
Perl界隈では最近AnyEventが盛り上がりを見せているらしく、今のうちに流行に乗ってみようかと思いちょっと触ってみました。

AnyEventは非同期というよりイベントドリブンなプログラムを作るための仕組みで、特徴的なのはイベントループのエンジンの切り替えが用意なことだそうです。
AnyEventで書けば、コードの多くを変更することなくバックエンドのイベントループのモジュールをEventやらEVやらに切り替えられるらしいです。

正直触ってみないとよくわからないので、参考サイトやらperldocやらを見て1秒おきに適当な文字列を出力しつつ、標準入力に入力があった場合はそれを標準出力に出力するようなプログラムを書いてみました。

#!/usr/bin/perl                                                                                                                                                                 
use strict;                                                                                                                                                                     
use AnyEvent;                                                                                                                                                                   
                                                                                                                                                                                
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;                                                                                                                                                                
                                                                                                                                                                                
$| = 1;                                                                                                                                                                         
while (1) {                                                                                                                                                                     
    my $cv = AnyEvent->condvar;                                                                                                                                                 
    my $wait_for_input; $wait_for_input= AnyEvent->io (                                                                                                                         
                                                       fh => \*STDIN,                                                                                                           
                                                       poll => 'r',                                                                                                             
                                                       cb => sub {                                                                                                              
                                                           $cv->send(scalar <STDIN>);                                                                                           
                                                       }                                                                                                                        
                                                   );                                                                                                                           
    print $cv->recv;                                                                                                                                                            
}                                                                                                                              

前半でイベントタイマーを作成しています。
intervalで指定した秒数ごとにcbで指定した関数が呼ばれます。

後半では、無限ループの中で標準入力からの入力があった場合のイベント監視を行うAnyEvent->ioのインスタンスを作成しています。
ここではタイマーのものとは別のcondvarを使用しています。

AnyEvent->condvarで取得しているのはConditionVariable(状態変数)と呼ばれるもので、メインループに状態を伝えるための変数です。
これにsendすることでイベントが呼び出されます。
それはrecvメソッドで受け取ることができ、コードを実行します。
sendメソッドには引数を渡すことができて、recvで受け取ることができます。
今回のコードでは標準入力から受け取った内容をsendで引数に指定してrecvで受け取っています。

イマイチまだ触りたてでいくつかしっくりきていない部分がありありです。
timerでsendとrecvをしているのですが、これなくても動くんです。
timerがどこかのイベントループ内にスケジューリングされているならrecvしようがsendしようがあんまり意味なさそうなんですが、どんなタイミングで使うんでしょうかね。
あとは、標準入力から受け取る部分が無限ループになっているので、後ろにコードがかけない。これは書き方の問題ですかね。

非同期プログラミングはものによってはパフォーマンスを上げるための良いツールになったり、非同期実行を行うようなジョブエンジン的なものとして使えたり、いろいろ便利なのでもう少しじっくり使ってみます。

参考

あわせて読みたい