AnyEvent::Intro チョー訳 その2

チョー訳その2です。今回はCondition Variablesとmain loop、それとTimer関連の説明部分を訳します。
前回の区切りの場所がイマイチ悪かったんですが、今回のところまでで「Introduction to Event-Based Programming」のパートが終わります。

なお部分的にかなり意訳、というか勝手な解釈で訳している箇所もあります。概ね合っているとは思いますが、こまかい部分では間違った表現もあるような気がしてます。

より正確に勉強したい人は原文を眺めながらチョー訳をご覧下さいませ。

原文はこちら
http://search.cpan.org/~mlehmann/AnyEvent-5.2/lib/AnyEvent/Intro.pod#Condition_Variables


それでは訳して行きましょう!

Condition Variables(状態変数)

I/Oウォッチャーの例に戻ってみます。そのコードはまだ不完全ですし、動かないでしょう。なぜならば、コールバックは出し抜けには実行されない為です。イベントループを走らせる必要があります。また、イベントベースプログラミングは時折ブロックする必要もあります。単に他にすべきことがない時や、いくつかのイベントのために全てを待たせる時、それらは同様に新しいイベントが到着するまでプロセスをブロックする必要があります。

AnyEventにおいては、これは状態変数(Condition Variables)を使うことでなされます。状態変数とは、初期状態では偽(False)でありそのうち真(TRUE)に満たされるべき状態を表現するので、「状態変数」と名付けられています。

またあなたは、それらを「マージポイント」「同期ポイント」「ランデブーポート」とも、もしくはコールバックやその他諸々とも呼ぶことができます(これらは他のフレームワークにおいて、しばしばこのように呼ばれます)。

あなたはこれらを自由に作ることができるし、後からTRUEになるのを待つことができる、ということが重要なポイントとなります。

状態変数は2つの側面を持ちます。一方は状態の"プロデューサ"(状態を判断しフラグ立てを行うあらゆるコード)で、もう片方は"消費者"(その状態を待っているコード)です。

先のセクションでの例においては、プロデューサはイベントコールバックでしたが、まだ消費者がいませんでした。では、今すぐ変更してみましょう。

use AnyEvent; 

$| = 1; print "enter your name> ";

my $name; 

my $name_ready = AnyEvent->condvar; 

my $wait_for_input = AnyEvent->io ( 
	fh => \*STDIN, 
	poll => "r",
	cb => sub {
		$name = <STDIN>;
		$name_ready->send;
	}
); 

# do something else here

# now wait until the name is available:
$name_ready->recv;

undef $wait_for_input; # watche rno longer needed

print "your name is $name\n";

このプログラムは、AnyEvent->condvarメソッドにより、AnyEventのcondvar(状態変数)を作っています。それから、たいていそうであるように、ウォッチャーを生成していますが、それはそのコールバックの内部において$name_ready状態変数のsendを呼んでいます。それは継続する為に待ち続けるあらゆる人を引き起こします。

このケースにおける”あらゆる人”とは、あとに続くコードを指します。それは$name_ready->recvを呼び出します。プロデューサはsendを呼び、消費者はrecvを呼びます。

もしも有効な$nameがまだないのならば、$name_ready->recv呼び出しは状態変数がTRUEになるまであなたのプログラムを停止させるでしょう。

名前を授受する意味として、あなたは実はこれを使ってデータを送ったり受け取ったりすることができます。たとえば、上記のコードはまた、名前を保持する為の余計な変数を使うことなく、このようにも書けます。

use AnyEvent; 

$| = 1; print "enter your name> ";

my $name_ready = AnyEvent->condvar;

my $wait_for_input = AnyEvent->io (
	fh => \*STDIN,
	poll => "r",
	cb => sub { $name_ready->send (scalar <STDIN>) }
); 

# do something else here 

# now wait and fetch the name
my $name = $name_ready->recv;

undef $wait_for_input; # watche rno longer needed

print "your name is $name\n";

あなたはsendに対していくつでも引数を渡せますし、recvを呼び出せば、全ての場所でそれらは返ってくるでしょう。

The "main loop"(メインループ)

たいていのイベントベースフレームワークは、"メインループ"、もしくは"イベントループ・ラン・ファンクション"、あるいは類似した名前で呼ばれる何かを持っています。

AnyEventのrecvにおいて見られるように、イベントループが実際にあなたの関心事であるこれらのイベントを探索する機会を得る為に、これらのファンクションは最終的に呼び出される必要があります。

例えば、Gtk2プログラムにおいて、上記の例は以下のようにも書けます。

use Gtk2 -init;
use AnyEvent; 

############################################ 
# create a window and some label

my $window = new Gtk2::Window "toplevel";
$window->add (my $label = new Gtk2::Label "soon replaced by name");

$window->show_all;

############################################
# do our AnyEvent stuff

$| = 1; print "enter your name> ";

my $name_ready = AnyEvent->condvar;

my $wait_for_input = AnyEvent->io (
	fh => \*STDIN,
	poll => "r",
	cb => sub {
		# set the label 
		$label->set_text (scalar <STDIN>);
		print "enter another name> ";
	}
);

############################################ 
# Now enter Gtk2's event loop

main Gtk2;

目に見える状態変数はどこにもありませんが、そのかわり標準入力から1行を読み込んでラベルのテキストに置き換えています。実際には、誰も$wait_for_inputをundefしていないので、あなたは複数行を入力できてしまいます。

状態変数を待つ替わりに、プログラムはGtk2->mainを呼び出すことでGtk2のメインループに入ります。それはプログラムをブロックしイベントの到着を待ちます。

また、これはAnyEventが非常に柔軟であることも示してします。AnyEventのウォッチャーにGtk2(実際にはGlib)を使わせるために、あなたがしなければならないことは何一つありません。それは単に動作します。

とはいえ、明らかにこの例は少々馬鹿げています。Gtk+アプリにおいて標準入力から名前を読み込みたいなんて、誰が願うでしょうか。

しかし、そのかわりに、あなたがバックグラウンドでHTTPリクエストを生成しその結果を表示するところを想像して下さい。

実際に、あなたはイベントベースプログラミングを使って、プログラム内で多くのHTTPリクエストを並列に生成しつつ、ユーザへそのフィードバックを示したり、ユーザとの対話状態を維持することが可能です。

次のパートでは、どのようにそれを実現するかを見て行くことになります。AnyEventともに提供されるユーテリティモジュールを使って、我々の書くプログラムの上で複数のHTTPリクエストを実装してみます。

しかしその前に、他のイベントループ・ラン・ファンクションを使わずに、AnyEventだけでどのようにプログラムを書けばいいのか、ちょっとだけ見ておきましょう。

状態変数を使った例において、我々はイベントを待ち受ける為にそれらを使いました。そして実際に状態変数はそれらの解決策です。

my $quit_program = AnyEvent->condvar;

# create AnyEvent watchers (or not) here

$quit_program->recv;

もしもウォッチャーのコールバックのいずれかがループの停止を決定するのであらば(それは他のフレームワークではしばしば"unloop"と呼ばれます)、ウオッチャー達はシンプルに$quit_program->sendを呼び出せます。

もちろん、彼らは単にexitを呼ぶことも、そうしないことも、もしくは永久に停止しないということも決定もできます(例えば長期間走るデーモンプログラムなど)。

もしもあなたがクリーンな停止機能を必要としていなくて、かつ、単にイベントループを走らせたいだけならば、シンプルにこのようにできます。

AnyEvent->condvar->recv;

そして、これは実際にAnyEventが提供するメインループ・ラン・ファンクションの考え方にもっとも近いものなのです。


Timers and other event source(タイマーとその他のイベントソース)

ここまではI/Oウォッチャーだけを使用してきました。これらは主に、Socketがデータを読み込めるか否か、もしくはもっとデータを書き込めるか否か、を知るために役立ちます。

ウィンドウ/ターミナルのコンソール(たいてい標準入力の上の)や、シリアル接続、その他色々の装置を動作させ得る、まっとうなオペレーティングシステムは、基本的にすべて、それ自身はフィルではない、ファイルディスクリプタを持っています。

("まっとうな"という部分にはwindowsは含まれません。windowsプラットフォームにおいては、これら全てについて異なる機能が必要となるでしょうし、それはひどく複雑なコードです。windowsにおいては、まっとうなのは"socketだけ"だと考えて下さい。)

しかしながら、I/Oが全てではありません。2番目に重要なイベントソースは時計です。例えば、HTTPリクエストを行うとき、サーバが事前に設定した時間内で答えを返さないのであれば、あなたはタイムアウト処理をのぞむでしょう。

AnyEventにおいて、タイマーイベントウォッチャーはAnyEvent->timerメソッドによって生成されます。

use AnyEvent;

my $cv = AnyEvent->condvar;

my $wait_one_and_a_half_seconds = AnyEvent->timer (
	after => 1.5, # after how many seconds to invoke the cb?
	cb => sub { # the callback to invoke
	$cv->send; 
	},
);

# can do something else here

# now wait till our time has come

$cv->recv;

I/Oウォッチャーとは異なり、タイマーは彼らが待った秒数にだけ関心を注ぎます。少なくとも、その時間が経過したとき、AnyEventはコールバックを実行します。データが到着する都度コールバックが呼ばれたI/Oウォッチャーとは異なり、タイマーは通常、1回限りのものです。それらが一度発射されコールバックが呼ばれた後では、タイマーは死に、もはや何も実行しません。

概ね1秒に1回発射されるような、反復型のタイマーを取得する為には、インターバル・パラメータを指定できます。

my $once_per_second = AnyEvent->timer (
	after => 0, # first invoke ASAP
	interval => 1, # then invoke every second
	cb => sub { # the callback to invoke
		$cv->send;
	},
);
  • More esoteric sources(より難解なソース)
    • AneyEventは、また他の、あなたが利用出来る、より難解なイベントソースも持っています。それは、シグナルや子プロセス、アイドル状態などのウォッチャーです。
    • シグナルウォッチャーは"シグナルイベント"を待つために使われます。それは単にプロセスが送られたシグナルを取得することを意味します(SIGTERMやSIGUSER1など)。
    • 子プロセスウォッチャーは子プロセスの終了を待ちます。別々のプロセスをフォークして、かつ、それらがいつ終了するのかを知る必要があるとき、これらは便利です。そしてあなたはブロッキングの間、待つことはありません。
    • イベントループが全ての外的なイベントをハンドリングして、新たなイベントを待つものの何も見つけられない時、すなわちプロセスがアイドル状態になっている時、アイドルウォッチャーはコールバックを実行します。
    • もしあなたが、プログラムが他にすべきことがない時に実行されるべきであるような、しかしあまり小さくはない何かしらのデータを処理したいのであれば、それは便利です。
    • これらすべてのタイプのウォッチャーについてはAnyEventのメインのマニュアルページにて詳述します。
    • 時折、あなたは現在の時刻を知る必要もあります。AnyEvent->nowはイベントツールキットが相対的なタイマーをスケジュールするための時間を返します。そしてそれはたいていあなたが望むものです。それはしばしばキャッシュされます(それは若干遅延しているかもしれないことを意味します)。そのようなケースでは、よりコストは高くつくが、AnyEvent->timeメソッドを使うことが可能です。それはオペレーティングシステムに現在時刻を問い合わせるので、遅いけど、より最新の時刻を返してくれます。

つづく