ブロッキングする処理を外部プロセスに任せる - AnyEvent::Workerを使ってみた

AnyEventを使う場合に、どうしてもブロッキングしてしまうような処理があるとして、それを外部プロセスとして切り出しつつ、メインのイベントループの中に取り込みたいんだよな、と。

そんな時はAnyEvent::Workerがよさそうです。

AnyEvent::Worker - Manage blocking task in external process
http://search.cpan.org/~mons/AnyEvent-Worker/

POEで言うところのPOE::Component::Genericのようなものらしいです。使いこなせるようになるといろいろと便利!

use strict;
use warnings;
use AnyEvent;
use AnyEvent::Worker;

$|++;

print _timestamp(), "開始しまーす\n";

my $cv = AnyEvent->condvar;

print _timestamp(), "AnyEvent::Workerで外部プロセス化", "\n";
my $worker = AnyEvent::Worker->new( ['My::App'] );

print _timestamp(), "ブロッキングする処理を投げる", "\n";
$worker->do(
    blocking_task => 'a happy new year!',
    sub {
        my ( $worker, $return_message ) = @_;
        print _timestamp(), "外部プロセスからかえってきたよ", "\n";
        $cv->send($return_message);
    }
);

print _timestamp(),
    "その間にメインのイベントループでは他の処理ができるんだぜ", "\n";
sleep 2;
print _timestamp(), "メインループでもわざと2秒眠ってみたフリ", "\n";

my $rec = $cv->recv;
print _timestamp(), $rec, "\n";

sub _timestamp {
    my ( $sec, $min, $hour, $day, $month, $year ) = localtime(time);
    sprintf("%02d時%02d分%02d秒\t", $hour, $min, $sec);
}

#----------

package My::App;

sub new {
    my $class = shift;
    my $self = bless {@_}, $class;
    return $self;
}

sub blocking_task {
    my $self    = shift;
    my $message = shift;

    for ( 1 .. 5 ) {
        print "\t", _timestamp(), "外部プロセスでブロッキングしてしまう処理", "\n";
        sleep 1;
    }
    return $message;
}

sub _timestamp {
    my ( $sec, $min, $hour, $day, $month, $year ) = localtime(time);
    sprintf("%02d時%02d分%02d秒\t", $hour, $min, $sec);
}


これを実行するとこうなります。
インデントして書いてある行は外部プロセスで実行した処理です。

imac:Hacks miki$ perl any_event_worker.pl 
07時55分09秒	開始しまーす
07時55分09秒	AnyEvent::Workerで外部プロセス化
07時55分09秒	ブロッキングする処理を投げる
07時55分09秒	その間にメインのイベントループでは他の処理ができるんだぜ
	07時55分09秒	外部プロセスでブロッキングしてしまう処理
	07時55分10秒	外部プロセスでブロッキングしてしまう処理
07時55分11秒	メインループでもわざと2秒眠ってみたフリ
	07時55分11秒	外部プロセスでブロッキングしてしまう処理
	07時55分12秒	外部プロセスでブロッキングしてしまう処理
	07時55分13秒	外部プロセスでブロッキングしてしまう処理
07時55分14秒	外部プロセスからかえってきたよ
07時55分14秒	a happy new year!

外部プロセスでは1秒スリープを5回まわしてます。
なので「ブロッキングする処理を投げる」から5秒経って「外部プロセスからかえってきたよ」が表示されてますね。

注目すべきはその5秒の間にメインのイベントループの中でも他の処理ができているという部分です。
これがAnyEvent::Workerの良いところですね。ふんづまる処理は外部プロセスに押し出してしまおう、という発想です。

あ、ちなみにメインのイベントループで2秒スリープしているのは演出のため、わざとです。
実際にはブロッキングするような処理をAnyEventの中で書いては意味ありませんので、そこんところヨロシク。

そして最後に「外部プロセスからかえってきたよ」「a happy new year!」ということで、
新年あけましておめでとうございます。今年もよろしくござ候ー。