1. ホーム
  2. スクリプト・コラム
  3. パール

perl AnyEventの簡単な紹介と始め方

2022-02-02 16:23:13

イベント指向プログラミング(イベントドリブンプログラミング)とは。

プログラミングにおけるすべてのプログラムは、イベントによって決定されます。ユーザーの操作(キーボード、マウス)、または他のプログラムやストリームの到着、あるいはオペレーティングシステムのイベント(ネットワークパケットの到着など)が実行のトリガーとなります。

イベント指向プログラミングは、コード(通常はプログラムの関数の先頭)がアプリケーションのメインループに明示的に割り当てられ、コード自体がイベントとイベント処理コードの2つのコンポーネントメソッドで構成されているコンピュータプログラムを書くこととも定義できる。

イベント指向プログラミングは、通常、3つのコンテキストで適用されます。

1. ユーザーインターフェース(グラフィックスを含む)のコントロールの作成
2. サーバーベースのアプリケーションを作成する
3. ゲームプログラミング時の複数オブジェクトの管理

私たちのシステムを管理する場合、そのようなアプリケーションは、1万人の同時接続(いわゆるC10k問題)を解決するためのサーバーアプリケーションのために、多くのイベント指向のプログラミングを使用します

AnyEventは、非常にパフォーマンスの良いイベント駆動型プログラムとして C10k 私たちが普段書いているような、手続きベースのプログラムのように。event1-> → event2-> → event3 とやっていくのです。このように

しかし、イベントベースは全く異なり、基本的にメイン処理に本体フレームを1つ持ち、プログラムの動作はイベントによって駆動させます。例えば、ウィンドウアプリケーションを使っています。最大化最小化という点はイベントベースで、最大化イベントを受信すると、最大化イベントを行うプログラムの部分が動き出します。初めから終わりまで実行されるわけではありません。だから、イベントベースのプログラムを読むときは、マインドマップを描くと理解しやすいよ。

イベントベースのプログラムのメリットとしては、100個のファイルをダウンロードして、その後に処理するといった非同期的なことをするときに使われるのが一般的です。ダウンロードと処理の各プロセスに対してイベントを書き、これらのイベントを同期的に実行することが可能です(ネットワークが接続され、ファイルへのIOを読み書きしている間待つことがポイントで、イベントはこれらの待ち時間を再利用するために使用されます)。
Perlのselectの機能をご存知でしょうか、ハンドルが読み書きできる状態になるまで待って、別の読み書きの操作をするというものです。イベントループも同様です。

AnyEventの入門書では、WATCHERSと条件文の2つに注目すればいいんだ。

ウォッチャ

selectには、"WATCHERS"というロールがあり、これはselect機能そのものである。
AnyEventでは、IOだけでなく他のイベントも監視することができます。それを使っていろいろなことができます。これは、常に何かを監視している人だと考えることができます。
監視するための基本的な内蔵物がいくつかあります("monitorers")。
TIMER : ある条件までの時間を監視し、その時間ごとに異なるイベントを実行する
I/Oです。IOが読み書きできるかどうかを監視し、それに対応するイベントを行うものである
IDLE: アイドル時に行うべきイベント
SIGNAL : さまざまな情報を監視し、対応するイベントを呼び出す
CHILD PROCESS(チャイルドプロセス)。サブルーチンの状態に対応するイベントを呼び出す

タイマウォッチャ

基本構文

コピーコード コードは以下の通りです。

AnyEvent->timer(
after => $seconds, # How long after to do the corresponding action.
interval => $seconds, # How often to callback after the above condition is in effect.
cb => $cb, # cb is shorthand for callback, so you see, as soon as the previous condition is reached, the function pointed to by cb => will be run.
;)

使用例です。

以下の例では、5秒後に$wがundefになるまで(つまり$count >が10回になるまで)、2秒ごとにコールバック内のイベントが実行されます。この場合の$wのundefは、このウォッチャーをキャンセルするためのものである。

コピーコード コードは以下の通りです。

#! /usr/bin/perl
use strict;
use AnyEvent;
my $cv = AnyEvent->condvar;
my $count = 0;
my $w; $w = AnyEvent->timer(
after => 5,
interval => 2,
cb => sub {
$count++;
warn "This is the first $count call";
if ($count >= 10) {
undef $w;
}
}
);
$cv->recv;

I/Oウォッチャー

基本的な構文

コピーコード コードは以下の通りです。

my $fh = .... ; # Open a handle
my $io; $io = AnyEvent->io(
fh => $fh, # The handle opened above, which can also be a standard input and output
poll => "w", # This is where you can select r and w for read and write IO events
cb => sub {
syswrite( $fh, "written content" );
undef $io;
}
);

使用例です。

次の例は、ioを使ってファイルが読めるかどうかを監視し、cb関数を呼び出して、ファイルが読み込まれてundefでイベントがキャンセルされるまで、1バイトずつ直接ファイルtest.txtを読み込むものです。

コピーコード コードは以下の通りです。

#! /usr/bin/perl
use strict;
use AnyEvent;
my $cv = AnyEvent->condvar;
open my $fh, "<test.txt" or die "Cannot open file handle $! ";
my $io; $io = AnyEvent->io(
fh => $fh,
poll => "r",
cb => sub {
my $len = sysread( $fh, my $buf, 1 );
if ($len > 0) {
print "read '$buf'\n";
}
else {
undef $io;
die "Read error: $! ";
}
});
$cv->recv;

IDLE WATCHERS

基本的な構文

コピーコード コードは以下の通りです。

my $w = AnyEvent->idle (cb => sub { ... });

使用例です。

次の例は、プログラム内で他のイベントが実行されていないときに、アイドルを実行します。これは、他のすべてのイベントが待機し、空になったときに呼び出されます。

コピーコード コードは以下の通りです。

#! /usr/bin/perl
use strict;
use AnyEvent;
my $cv = AnyEvent->condvar;
my $t; $t = AnyEvent->timer(
after => 1,
interval => 1,
cb => sub { print time(). "\n" }
);
my $w; $w = AnyEvent->idle(
cb => sub {
warn "idle";
# undef $w;
}
);
$cv->recv;

シグナルウォッチャー

基本的な構文は以下の通りで、POSIXシグナルを受信したときにコールバックでイベントを実行するというものです。

コピーコード コードは以下の通りです。

my $w = AnyEvent->signal (signal => "TERM", cb => sub { ... });

チャイルド・プロクルズ・ウォッチャー

基本的な構文は次のとおりです。

コピーコード コードは以下の通りです。

# child process exit
my $w = AnyEvent->child (pid => $pid, cb => sub {
my ($pid, $status) = @_;
...
});

条件付き変数(複数の条件が存在する場合)

上記のイベント監視を学んだ後のAnyEventでは必須です。上記のAnyEvent->condvar;と$cv->recvを見たことがあると思いますが、condvarは条件変数の略称です。どのような条件が成立したときに、どのような変数になるかということです。

は、実際には、条件を満たしたときにイベントループを抜ける条件です。そのため、従来のイベントのようなループ関数はAnyEventには存在しません。つまり、条件変数を使うことは、イベントをループさせることと同じなのです。

基本的な $cv->recv は $cv->send と対になっているので、イベントが send を呼び出すと、recv がその呼び出しを受けなければイベントは終了しません。

次の $cv->begin と $cv->end は基本的に同じものです。send は単一の条件です。begin と end は複数の条件が満たされたときに終了する、言い換えれば、イベントがすべてペアになったときに終了する条件です。

コピーコード コードは以下の通りです。

#! /usr/bin/perl
use strict;
use AnyEvent;
my $cv = AnyEvent->condvar( cb => sub {
warn "End of call";
});
for my $i (1..10) {
$cv->begin;
my $w; $w = AnyEvent->timer(after => $i, cb => sub {
warn "finished timer $i";
undef $w;
$cv-> end;
});
}
$cv->recv;

デフォルトのcondvarはイベントにfalseの条件を作るので、direct sendとbegin sendがあった場合のみtrueになり、イベントループを抜けることになります。これはセマフォと考えるのがよいでしょう。
条件が真でない場合、イベントはAnyEventでループし続けます。したがって、上記の例では、送信は行われません。

AnyEventの詳細については、一度AnyEvent::HTTP、twiggyなどを使って遊んでみてください。これらのアプリケーションとプロジェクトをチェックしてください。

また、AnyEvent では、EV をよく使います。これはC言語のlibevに対するPerlのインターフェースで、非常に高いパフォーマンスを持っています。上記を読んだ後、下記のEVの使い方をみてください。とても簡単で、基本的に同じです。条件変数が表示されないだけです。
従来のEV::loop;を使用しています。

コピーコード コードは以下の通りです。

Use EV;
# TIMERS
my $w = EV::timer 2, 0, sub {
warn "is called after 2s";
};
my $w = EV::timer 2, 2, sub {
warn "is called roughly every 2s (repeat = 2)";
};
undef $w; # destroy event watcher again
my $w = EV::periodic 0, 60, 0, sub {
warn "is called every minute, on the minute, exactly";
};
# IO
my $w = EV::io *STDIN, EV::READ, sub {
my ($w, $revents) = @_; # all callbacks receive the watcher and event mask
warn "stdin is readable, you entered: ", <STDIN>;
};
# SIGNALS
my $w = EV::signal 'QUIT', sub {
warn "sigquit received\n";
};
# CHILD/PID STATUS CHANGES
my $w = EV::child 666, 0, sub {
my ($w, $revents) = @_;
my $status = $w->rstatus;
};
# STAT CHANGES
my $w = EV::stat "/etc/passwd", 10, sub {
my ($w, $revents) = @_;
warn $w->path, " has changed somehow.\n";
};
# MAINLOOP
EV::loop; # loop until EV::unloop is called or all watchers stop
EV::loop EV::LOOP_ONESHOT; # block until at least one event could be handled
EV::loop EV::LOOP_NONBLOCK; # try to handle the same events, but do not block

注:この記事の内容のほとんどは、日本の@lestrratからのものです。