非同期プログラミング
   with Perl

           2010/08/07
   Japan Perl Association 代表理事
         株式会社ライブドア
    牧    大 輔 (@lestrrat)
さっそくですが


 「複数URLに接続して
HTTP GETするコード」
普通の書き方
use strict;
use LWP::UserAgent;

my @urls = qw(
   http://www.livedoor.com/
   http://www.dena.jp/
   http://mixi.jp/
   http://www.gaiax.co.jp/
);

my $ua = LWP::UserAgent->new();
foreach my $url (@urls) {
   my $res = $ua->get( $url );
   ...
}
ポイント
•シンプル!
•命令を順番に処理
•ソケットからの読み込みに時間がか
かると次の処理に進めない
理想
•とりあえず可能な限りのホストに接続
•接続できたらとりあえずHTTPリクエ
スト発行
•読み込み可能なところ(先に返信が来
たところから)読み込む
read
•read(2) は読み込めるデータが到着
するまでブロックする
•一個だけ遅いホストがあると全体が
遅くなる :/
効率よくread
効率よくread

     ソケット1
効率よくread

     ソケット1

     ソケット2
効率よくread

     ソケット1

     ソケット2

     ソケット3
効率よくread

    読める?     ソケット1
  (データ来た?)
             ソケット2

             ソケット3
効率よくread

             ソケット1

    読める?     ソケット2
  (データ来た?)
             ソケット3
効率よくread

             ソケット1

             ソケット2

    読める?     ソケット3
  (データ来た?)
効率よくread

             ソケット1

             ソケット2

    読める?
    来た!      ソケット3
  (データ来た?)
   読み込め!
効率よくread

             ソケット1

             ソケット2

    読める?
  (データ来た?)
効率よくread

    読める?     ソケット1
  (データ来た?)
             ソケット2
効率よくread

    読める?
     来た!     ソケット1
  (データ来た?)
    読み込め!
             ソケット2
効率よくread

    読める?
  (データ来た?)
             ソケット2
効率よくread

    読める?     ソケット2
  (データ来た?)
効率よくread

     来た!
    読める?     ソケット2
    読み込め!
  (データ来た?)
効率よくread

    読める?
  (データ来た?)
効率よくread
イベント駆動
メインループ
(プログラムの進行)
 を他人に任せる
イベントループ




          =
              while ( $still_alive ) {

                  ... 処理 ...

              }
ループから呼ばれる
コールバックを登録する
イベントループ           キュー

                  コールバック   実行!
                  コールバック


                  コールバック


                  コールバック




          このイベントを待っている
イベント発生!
          コールバックがあるか確認
イベント?非同期?
• イベント駆動の仕組みの中でI/O処理→ファイル
ハンドル等を「非同期モード」にする


• イベント駆動のメリットを生かすにはI/O等ブロッ
クする処理が多いときに使う


•よって「非同期プログラミング」と「イベント駆動
プログラミング」は同義で使うことが多い
AnyEvent
なんで?
POE
Danga::Socket
IO::Async
Event
Glib Qt
お互いに
互換性無し
AnyEvent
非同期フレームワークのラッパ
汎用API           これだけ覚えていればOK




              AnyEvent
デフォルト




                   Event




                                  その他
                           Glib
        POE

              EV
standard API++
混ぜることもできる

 例:POE+EV
新規に書くなら
AnyEventでおk
先にお知らせ
名前空間
AnyEvent
   vs
   AE
スタイルの違いだけ
AnyEvent->timer(
    after => $after,
    interval => $interval,
    cb => sub {
        ....
    }
);



AE::timer $after, $interval, sub {
    ...
};
ここではAEを
 使います
基本コンポーネント

•ループ
•ウォッチャー
•コンディション変数
•ガード
ウォッチャー
AnyEventコンポーネント

        IO    コールバック
                       実行!
       タイマー   コールバック




                 「ウォッチャー」
AnyEventを使う =
 ウォッチャーの管理をする
Timer
my $timer;
$timer = AE::timer 0, 1, sub {
    warn “timer invoked”;
    undef $timer;
};
I/O
my $io;
$io = AE::io $fh, $read_or_write, sub {
    .... # $fhから読んだり、$fhに書いたり
    undef $io;
};



          注意:$fhは非同期モードに指定しておく
fh_nonblocking

use AnyEvent::Util qw(fh_nonblocking);

my $fh = get_socket(...);
fh_nonblocking $fh, 1;

my $io;
$io = AE::io $fh, 0, sub { ... };
シグナル
my $sig;
$sig = AE::signal "TERM", sub {
    ...
    undef $sig;
};
子プロセス
my $child;
$child = AE::child $pid, sub {
    ...
    undef $child;
};
待機状態
my $idle;
$idle = AE::idle sub {
    ...
    undef $idle;
};
なにこれ?
my $timer;
$timer = AE::timer 0, 1, sub {
    warn “timer invoked”;
    undef $timer;
};
Perl:
スコープが終わると
メモリが解放される
AnyEvent:
ウォッチャーが解放されると
イベントがキャンセルされる
NG
{
    my $timer = AE::timer 0, 1, sub {
        warn “timer invoked”; # 走らない!
    };
} # ここにたどり着いた時点で $timerが解放




       スコープ終了。リソース解放され
        $timerも解放されてしまう
Good
my $timer;
$timer = AE::timer 0, 1, sub {
    ...           # どこかで $timer を使うコード
    undef $timer; # 明示的に解放しないと消えない
}



    クロージャで使用されているため
    明示的に解放されるまで生き残る
コンディション変数
• いくつかの違う機能が同居してる
• ちょっと混乱しやすい
• が、重要
• 以降 「condvar」と表記
1. 何かを待つ
•現処理の流れを「止める」
•「知らせ」を待つ
•スクリプトレベルでは「ループに制御
を渡す」
#!perl
use strict;
use AnyEvent;

my $cv = AE::cv {
   print "Endn";
};
my $timer;
$timer = AE::timer 10, 0, sub {
   print "Waited 10 seconds!n";
   undef $timer;
   $cv->send;
};

$cv->recv;
#!perl
use strict;
use AnyEvent;

my $cv = AE::cv {
   print "Endn";
};
my $timer;
$timer = AE::timer 10, 0, sub {
   print "Waited 10 seconds!n";
   undef $timer;
   $cv->send;
};

$cv->recv;


             知らせ が来るまでこの次にはいかない
               (→イベントループが起動)
#!perl
use strict;
use AnyEvent;

my $cv = AE::cv {
   print "Endn";
};
my $timer;
$timer = AE::timer 10, 0, sub {
   print "Waited 10 seconds!n";
   undef $timer;
   $cv->send;      タイマーが起動したら 知らせ を送る
};

$cv->recv;
#!perl
use strict;
use AnyEvent;

my $cv = AE::cv {
   print "Endn";
};
my $timer;
$timer = AE::timer 10, 0, sub {
   print "Waited 10 seconds!n";
   undef $timer;
   $cv->send;
};

$cv->recv;


        知らせ が来たら次の処理へ
         (→スクリプト終了)
#!perl
use strict;
use AnyEvent;

my $cv = AE::cv {
   print "Endn";
};
my $timer;
$timer = AE::timer 10, 0, sub {
   print "Waited 10 seconds!n";
   undef $timer;
   $cv->send;
};

$cv->recv;
#!perl
use strict;
use AnyEvent;

my $timer;
$timer = AE::timer 10, 0, sub {
   print "Waited 10 seconds!n";
   undef $timer;
};
#!perl
use strict;
use AnyEvent;

my $timer;
$timer = AE::timer 10, 0, sub {
   print "Waited 10 seconds!n";
   undef $timer;
};



            何も「待つ」ことがなかったので
             イベントループも起動しない
#!perl
use strict;
use AnyEvent;

my $timer;
$timer = AE::timer 10, 0, sub {
   print "Waited 10 seconds!n";
   undef $timer;
};
2. 複数の知らせを待つ

•フラグをあげる→落とす
•一つでもフラグがあがっていれば待つ
•フラグが全部落ちるとお知らせ
#!perl
use strict;
use AnyEvent;

my @delay = (1, 2, 5, 10);

my $cv = AE::cv { print "All timers are donen" };

foreach my $delay (@delay) {
   my $timer;
   $cv->begin;
   $timer = AE::timer $delay, 0, sub {
      print "Timer for delay = $delayn";
      undef $timer;
      $cv->end;
   };
}

$cv->recv;
#!perl
use strict;
use AnyEvent;

my @delay = (1, 2, 5, 10);

my $cv = AE::cv { print "All timers are donen" };

foreach my $delay (@delay) {
   my $timer;
   $cv->begin;             フラグをあげる
   $timer = AE::timer $delay, 0, sub {
      print "Timer for delay = $delayn";
      undef $timer;
      $cv->end;
   };
}

$cv->recv;
#!perl
use strict;
use AnyEvent;

my @delay = (1, 2, 5, 10);

my $cv = AE::cv { print "All timers are donen" };

foreach my $delay (@delay) {
   my $timer;
   $cv->begin;
   $timer = AE::timer $delay, 0, sub {
      print "Timer for delay = $delayn";
      undef $timer;
      $cv->end;             フラグを落とす
   };
}

$cv->recv;
#!perl
use strict;
use AnyEvent;

my @delay = (1, 2, 5, 10);

my $cv = AE::cv { print "All timers are donen" };

foreach my $delay (@delay) {
   my $timer;
   $cv->begin;
   $timer = AE::timer $delay, 0, sub {
      print "Timer for delay = $delayn";
      undef $timer;
      $cv->end;
   };
}

$cv->recv;        フラグが全部落ちたらお知らせ
#!perl
use strict;
use AnyEvent;

my @delay = (1, 2, 5, 10);

my $cv = AE::cv { print "All timers are donen" };

foreach my $delay (@delay) {
   my $timer;
   $cv->begin;
   $timer = AE::timer $delay, 0, sub {
      print "Timer for delay = $delayn";
      undef $timer;
      $cv->end;
   };
}

$cv->recv;
3. 処理終了時の
     コールバック
•何か戻り値が必要な時処理結果を待つ
•ウォッチャーを作る関数に知らせて欲
しいcondvarを渡す
#!perl
use strict;
use AnyEvent;

sub add {
   my ($x, $y, $cv) = @_;
   my $timer;
   $timer = AE::timer 5, 0, sub {
       $cv->send( $x + $y );
       undef $timer;
   };
}

my $cv = AE::cv {
   my $cv = shift;
   my ($result) = $cv->recv;
   print "Result = $resultn";
};

add( 3, 2, $cv );
$cv->recv;
#!perl
use strict;
use AnyEvent;

sub add {
   my ($x, $y, $cv) = @_;
   my $timer;
   $timer = AE::timer 5, 0, sub {
       $cv->send( $x + $y );
       undef $timer;
   };
}

my $cv = AE::cv {
   my $cv = shift;
   my ($result) = $cv->recv;
   print "Result = $resultn";
};

add( 3, 2, $cv );      結果を受け取るコールバックを渡す
$cv->recv;
#!perl
use strict;
use AnyEvent;

sub add {
   my ($x, $y, $cv) = @_;
   my $timer;
   $timer = AE::timer 5, 0, sub {
       $cv->send( $x + $y );        計算結果をcondvarに渡す
       undef $timer;
   };
}

my $cv = AE::cv {
   my $cv = shift;
   my ($result) = $cv->recv;
   print "Result = $resultn";
};

add( 3, 2, $cv );
$cv->recv;
#!perl
use strict;
use AnyEvent;

sub add {
   my ($x, $y, $cv) = @_;
   my $timer;
   $timer = AE::timer 5, 0, sub {
       $cv->send( $x + $y );
       undef $timer;
   };
}

my $cv = AE::cv {
   my $cv = shift;
   my ($result) = $cv->recv;        結果を受け取る
   print "Result = $resultn";
};

add( 3, 2, $cv );
$cv->recv;
#!perl
use strict;
use AnyEvent;

sub add {
   my ($x, $y, $cv) = @_;
   my $timer;
   $timer = AE::timer 5, 0, sub {
       $cv->send( $x + $y );
       undef $timer;
   };
}

my $cv = AE::cv {
   my $cv = shift;
   my ($result) = $cv->recv;
   print "Result = $resultn";
};

add( 3, 2, $cv );
$cv->recv;
基本はこれだけ
•ウォッチャーとcondvarだけで基本
的に全てまかなえる
•実際にはこれらを使った高レベルライ
ブラリを使う
AnyEventで


 「複数URLに接続して
HTTP GETするコード」
AnyEvent::HTTP
use strict;
use AnyEvent;
use AnyEvent::HTTP;

my @urls = qw(
   http://www.livedoor.com/
   http://www.dena.jp/
   http://mixi.jp/
   http://www.gaiax.co.jp/
);

my $cv = AE::cv {
   print "Fetched all urls!n";
};

my $guard;
foreach my $url (@urls) {
   $cv->begin;
   $guard = http_get $url, sub {
      print "Got $urln";
      undef $guard;
      $cv->end;
   }
}
$cv->recv;
以上!
•万が一どれかが遅くても、他のURL
は先に処理される
ライブラリ
 AnyEvent::Twitter          AnyEvent::Memcached     AnyEvent::FriendFeed::Realtime




AnyEvent::Twitter::Stream   AnyEvent::ReverseHTTP   AnyEvent::HTTP::MXHR



  AnyEvent::CouchDB         AnyEvent::SuperFeedr       AnyEvent::AIO


  AnyEvent::BDB              AnyEvent::DNS            AnyEvent::Beanstalk



   AnyEvent::DBI            AnyEvent::SNMP             AnyEvent::MP


AnyEvent::Gearman           AnyEvent::XMPP          Cache::Memcached::AnyEvent
Twiggy
Plack用非同期HTTPサーバー
1 スレッド!
複数接続高速処理
plackup -s Twiggy ...
ちょっとずつレスポンス
use strict;
use AnyEvent;

sub {
   my $env = shift;
   return sub {
      my $start_response = shift;
      my $writer = $start_response->( [
         200,
         [ "Content-Type" => "text/plain" ]
      ]);

      my $count = 1;
      my $t; $t = AE::timer 2, 2, sub {
         $writer->write( ($count * 2) . "秒たったよ!n");
         if ($count++ == 5) {
            undef $t;
            $writer->close;
         }
      };
   }
}
用途


                非同期サーバー
•接続後切断しない or              •何らかのイベント待っている
•いつ来るかわからないデー             •メッセージキュー、速いか遅
タを待っている                   いかわからないサービス等
プロキシ
•外部サーバーと連携するサービス
  •サーバーの性能を自分でコントロールできな
  い場合など
•某SNSとか某SNSとか
イベントサーバー
•ブラウザゲーム
•複数のクライアントが同時にイベント
を共有する場合など
注意
•「やりたいから」非同期にすると失敗
する
•「必要があるから」使うべき
•非同期コードは難しくなる
非同期にする条件
• リアルタイム性が重要
  • いつ起こるか分からない
  •ポーリングではリクエスト回数が多すぎる
• I/O待ちが多い
  •データを常時送り続けているのは x
  •待ち時間が長いとメリットがある
非同期にしない理由
•コードが複雑になる
•コードが複雑になる
•コードが複雑になる
•コードが複雑になる
•コードが複雑になる
必要な時だけ使う
  =効果絶大
Questions?
ご静聴ありがとう
 ございました

Perl 非同期プログラミング