POE おぼえがき

TIMTOWTDI なことがいっぱいで POE のことがよくわからなかったのですが,Cookbook とリファレンスを読んでなんとなくわかってきました。

  • POE::Kernel が POE システムのコアで唯一神
    • POE::Kernel->run()*1 を実行するとイベントループが回り出す
    • イベントループでやることがなくなると POE::Kernel->run() から戻る(戻ることを期待してはいけない)
  • POE::Session が POE システムにおける,タスク/ステートマシン/プロセス/スレッド的なものの単位
    • 複数生成してよい
    • セッションの最初に _start イベントが発生する
    • セッションの最後に _end イベントが発生する
    • イベントハンドラとして,サブルーチンリファレンス(inline_states),パッケージ関数(package_states),オブジェクトメソッド(objects_states)を登録できる
  • イベントの生成について
    • 自セッションに対してイベントを送る場合 $_[KERNEL]->yield()
    • 他セッションに対してイベントを送る場合 $_[KERNEL]->post()
    • 他セッションにリアルタイムイベントを処理させる場合 $_[KERNEL]->call()
    • ある時刻に自セッションのイベントを発生させる場合 $_[KERNEL]->alarm()
    • ある時間後に自セッションのイベントを発生させる場合 $_[KERNEL]->delay()
      • sleep 代わりに使う
  • (おもに)非同期 I/O を利用したい場合に POE::Wheel::* を使う
    • プログラムを実行したいなら POE::Wheel::Run
    • 端末からの入力なら POE::Wheel::ReadLine
    • ソケットの listen / accept なら POE::Wheel::ListenAccept
    • ナドナド
  • POE::Component::* は高次機能コンポーネント(POE::Session や POE::Wheel::* 等をラッピングしてくれる)
    • POE::Component::Child でプログラムを楽に実行
    • POE::Component::Server::TCP で汎用 TCP サーバを楽に実装
    • POE::Component::Server::HTTP で HTTP サーバを楽に実装
    • ナドナド

イベントベースのプログラムとはなんぞやということは,Windows 3.1 のプログラムについて勉強すればわかります。というのは冗談として,POE::Session だけを使う単純なプログラムを作ってみました。

my $counter = 3;

while ($counter -- > 0) {
  print $counter, "\n";
}

sleep 5;

こんな感じのプログラムをイベントドリブンに書いてみます。一番わかりやすいであろう inline_states を使ったソースがこちら。

#!/usr/bin/perl

use strict;
use warnings;
use Smart::Comments;

use POE qw( Session );

POE::Session->create(
    inline_states => {
        _start      => \&sess_start,
        _stop       => \&sess_stop,
        
        first_stage => \&first_stage,
        loop_check  => \&loop_check,
        loop_do     => \&loop_do,
        loop_end    => \&loop_end,
        do_sleep    => \&do_sleep,
        final_stage => \&final_stage,
    },
);

### before event loop ...

POE::Kernel->run();

### after event loop ...

exit();

sub sess_start {
    ### _start ...
    $_[KERNEL]->yield('first_stage');
}

sub sess_stop  {
    ### _stop ...
}

sub first_stage {
    ### first_stage ...
    
    $_[HEAP]->{counter} = 3;
    
    $_[KERNEL]->yield('loop_check');
}

sub loop_check {
    ### loop_check ...
    
    if ($_[HEAP]->{counter} > 0) {
        $_[KERNEL]->yield('loop_do');
    }
    else {
        $_[KERNEL]->yield('loop_end');
    }
    
    $_[HEAP]->{counter} --;
}

sub loop_do {
    ### loop_do ...
    
    print {\*STDERR} "counter: ", $_[HEAP]->{counter}, "\n";
    
    $_[KERNEL]->yield('loop_check');
}

sub loop_end {
    ### loop_end ...
    
    $_[KERNEL]->yield('do_sleep');
}

sub do_sleep {
    ### do_sleep ...
    
    # sleep 5 seconds
    $_[KERNEL]->delay('final_stage', 5);
}

sub final_stage {
    ### final_stage ...
}

HEAP というのは各セッションで自由につかってよいハッシュリファレンスでセッション内で使えるスクラッチパッドのようなものです。また,どこを実行しているのかは Smart::Comments を利用して表示してみました。実行結果は以下の通り(空行を抜いています)。

### _start ...
### before event loop ...
### first_stage ...
### loop_check ...
### loop_do ...
counter: 2
### loop_check ...
### loop_do ...
counter: 1
### loop_check ...
### loop_do ...
counter: 0
### loop_check ...
### loop_end ...
### do_sleep ...
### final_stage ...
### _stop ...
### after event loop ...

_start イベントは POE::Kernel->run() 以前に実行するんですね。知りませんでした。

他のセッションとのやりとりや TCP/IP 通信等行っていないので POE のうまみがまったくわからないサンプルになってしまいましたが,根本的な考え方についてはわかるんではないかと。

*1:インスタンス $poe_kernel を使って $poe_kernel->run() とも書けます