AnyEventでイベント駆動プログラミング

Web+DB PRESSのvol.56にあったid:lestrratさんの「AnyEventでイベント駆動プログラミング」を読んで、イベント駆動プログラミングに入門してみました。
間違っている点もあるかと思うのでご指摘頂けると幸いです。

イベント駆動プログラミングとは?

  • 処理を記述された順番に実行するのではなく、「イベントループ」と呼ばれる処理のタイミングを管理する担当者に、「したい処理」と「処理をしたいタイミング」を伝えることにより、「イベントループ」が処理をお願いしたタイミングで「したい処理」を実行してくれるというものです。

何がうれしいのか。

  • 強力的マルチタスキング。
  • 1つのプロセスで並列処理を行う。
    • あるウェブページのRSS「取得」し「解析」する場合、RSSを「解析」する処理はRSSを「取得」するまで行うことが出来ません。ですが、RSSを取得する処理は時間が掛かるので、それを待っている時間は無駄です。
    • そこで、「イベントループ」に「『RSSを解析する処理』を『RSSを取得した後』に実行してください」とお願いすることで、RSSを取得する処理を待つことなく他の処理を実行出来るので効率がいいという利点があります。

複数ページのRSSを取得するサンプル

LWP::UserAgentで取得する場合
#!/usr/bin/env perl

use strict;
use warnings;
use utf8;

use 5.010;
use Encode;
use LWP::UserAgent;
use XML::Simple;

my @uri_list = qw{ 
    http://d.hatena.ne.jp/koba04/rss2
    http://tower.jp/feeds/article/
    http://ws.audioscrobbler.com/1.0/user/koba04/recenttracks.rss
    http://atnd.org/events.rss
    http://rss.rssad.jp/rss/codezine/new/20/index.xml
};

my $ua = LWP::UserAgent->new;
$ua->timeout(10);
for my $uri (@uri_list) {
    say "HTTP Request => $uri";
    my $response = $ua->get($uri);
    if ($response->is_success) {
        my $res = XMLin($response->decoded_content);
        say encode_utf8($res->{channel}->{item}->[0]->{title});
    } else {
        die $response->status_line;
    }   
}
  • 結果です。1件ずつ順番にHTTPリクエストを出して、レスポンスを受け取った後にまた次のリクエストを出していることがわかります。
HTTP Request => http://d.hatena.ne.jp/koba04/rss2
[勉強会/セミナー]Lightweight Language Tiger
HTTP Request => http://tower.jp/feeds/article/
えっ、こんなこともできちゃうの!?
HTTP Request => http://ws.audioscrobbler.com/1.0/user/koba04/recenttracks.rss
New Order – Regret
HTTP Request => http://atnd.org/events.rss
第2回中国GTUG勉強会@岡山
HTTP Request => http://rss.rssad.jp/rss/codezine/new/20/index.xml
TIFFライブラリに潜む脆弱性をつぶすパッチ(その2)
AnyEvent::HTTPで取得した場合
  • わかりやすい(!?)ようにおかあさんがこどもをおつかいに出すことに例えてみました。。
#!/usr/bin/env perl

use strict;
use warnings;
use utf8;

use 5.010;
use AnyEvent;
use AnyEvent::HTTP;
use Encode;
use XML::Simple;

my @uri_list = qw{ 
    http://d.hatena.ne.jp/koba04/rss2
    http://tower.jp/feeds/article/
    http://ws.audioscrobbler.com/1.0/user/koba04/recenttracks.rss
    http://atnd.org/events.rss
    http://rss.rssad.jp/rss/codezine/new/20/index.xml
};

# おかあさん($cv)を作成
my $cv = AnyEvent->condvar;
for my $uri (@uri_list) {
    say "HTTP Request => $uri";
    # こどもにおつかいをお願いする
    $cv->begin;
    # こどもに指示を出す。
    my $guard; $guard = http_get $uri => sub {
        my ($body, $header) = @_; 
        # 明示的にメモリから解放するタイミングを指定してやる必要がある??
        undef $guard;
        my $res = XMLin(decode_utf8($body));
        # おかあさんに結果報告
        say encode_utf8($res->{channel}->{item}->[0]->{title});
        # こどもが帰ってきたことを確認する
        $cv->end;
    };  
}
# 全員こどもが帰ってくるのを確認するまで待つ(この場合は5人)
$cv->recv;
  • 結果です。HTTPリクエストがどんどん投げられて、返ってきたものから出力されていることがわかります。
  • 最初のLWP::UserAgentの場合だと、こどもが一人帰ってきたら次の一人をおつかいに出すようになっていますが、AnyEvent::HTTPの場合は、こどもをどんどん同時におつかいに出しているので、処理が短く済みます。
HTTP Request => http://d.hatena.ne.jp/koba04/rss2
HTTP Request => http://tower.jp/feeds/article/
HTTP Request => http://ws.audioscrobbler.com/1.0/user/koba04/recenttracks.rss
HTTP Request => http://atnd.org/events.rss
HTTP Request => http://rss.rssad.jp/rss/codezine/new/20/index.xml
TIFFライブラリに潜む脆弱性をつぶすパッチ(その2)
PAIP独書会
[勉強会/セミナー]Lightweight Language Tiger
えっ、こんなこともできちゃうの!?
New Order – Regret
まとめ
  • 通信やファイルの読み書きなど、時間がかかるような処理を行う場合は、うまくAnyEventを使うことで短時間で処理することが出来たりと便利そうですね。
  • ここでは紹介していませんが、「何秒経ったら実行する」や「ファイルやソケットが書き(読み)込み可能になったら」なども出来るので、うまく使えばとても面白いものが作れそうですね。イマイチわかってない部分もあるのでもっと勉強します。
  • なんと、今回参考にさせて頂いた連載の内容はWebで公開されていますのでそちらを参照ください。すごくわかりやすいです。
余談
  • 上記のLWPの方のコードは、「perldoc LWP::UserAgent」の結果から取ってきたのですが、そのまま貼付けたときに、下記のようなエラーが出てちょっと焦りました。
Unrecognized character \xE2; marked by <-- HERE after :UserAgent<-- HERE near column 24 at test.pl line 9.
  • よく見てみるとソースの「->」の「-(HYPHEN-MINUS, Ux002D)」が「−(MINUS-SIGN, Ux2212)」になっているためエラーになっていました。(Mac OSX)