unoh.github.com

PHP版 Parallel::Prefork で奥一穂さんと親に感謝しよう

Wed Mar 17 18:56:28 -0700 2010

こんちにわ、去年末に入社した「ちわ」です、こんにちわ。

Perl には CPAN というものがあり、そこには様々なライブラリが登録されています。国内の方々も多くライブラリを登録されていますがその中で牧大輔さんQueue::Q4M奥一穂さんParallel::Prefork を PHP に移植したので今回は奥一穂さんの Parallel::Prefork のPHP版を紹介したいと思います。(Queue::Q4M はインターフェースを若干変更して移植してしまったので今回は Parallel::Prefork のみ紹介となります)

弊社が提供しているサービスの「まちつく!mixi版」、「まちつく!モバゲー版」の地図を生成、Amazon S3 への転送をキューを使って処理していまして、そのキュー処理に Q4M を導入することになりました。キュー処理の主な流れは下記のようになります。

主な処理の流れ


地図を生成するデーモンは OpenMP が有効になっている ImageMagick を1プロセスで起動しますが、地図を Amazon S3 へ転送するデーモンはHTTP通信を行うのでマルチプロセスで起動してパフォーマンスを稼ぐ必要があります。
ウノウに入社するまでずっと Perl のエンジニアだったこともあり、この手の処理は Queue::Q4M + Parallel::Prefork で実装し daemontools で起動したいところですがご存知の通りウノウは Perl な会社ではありません。PHP を使うのもの初めてということもあり、PHP の文法などの勉強がてら Queue::Q4M と Parallel::Prefork (0.05) を PHP に移植することにしました。
実際にサービスで使用しているものとは若干違いはありますがソースは github に置いてあります。

Parallel::Prefork の使い方は作者の奥一穂さんのページか CPAN の Parallel::Prefork のドキュメントをご覧ください。
Parallel::Prefork の SYNOPSIS のサンプルコードを Parallel_Prefork で書くとするとこうなります。



次に実際に動く単純なサンプルコードを紹介します。



10行目のコンストラクタで Parallel_Prefork のオブジェクトを生成しています。max_workers は生成したい子プロセスを指定します。このコンストラクタで重要な引数の trap_signals には

trap_signals => array(
    親プロセスで捕捉したいシグナル => そのシグナルを捕捉した場合に子プロセスに送りたいシグナル
)


といった配列を指定します。例では、

trap_signals => array(
    SIGHUP  => SIGTERM,
    SIGTERM => SIGTERM,
)


と指定いるので SIGHUP, SIGTERM を親プロセスが捕捉したら子プロセスに SIGTERM を送ることになります。Parallel_Prefork では内部でシグナルハンドラを上書きしているので上記の例だと親プロセスが SIGHUP, SIGTERM を捕捉してもそれ自体でプロセスが終了してしまうことはありません。

次に19行目の $pp->signalReceived() ですが、このメソッドは親プロセスが捕捉したシグナルが格納されていますので while は親プロセスが SIGTERM を捕捉するまでループします。

21行目の $pp->start() で子プロセスが max_workers で指定した数まで fork されます。$pp->start() は親プロセスがコールした場合は true を返しますので while の中では親プロセスが23行目以降のコードを実行することはありません。

24行目以降が子プロセスが実行するコードになります。例ではそれぞれの子プロセスがfor文で1〜3秒 sleep して $pp->finish() で exit します。

お子様プロセスがのんきに sleep している間、親プロセスは $pp->start() メソッドの中でお子様プロセスの数が max_workers をキープできるようにお子様プロセスの fork と wait、シグナルが送られていないかなどの監視にせっせと励んでいます。本当、親には感謝しないといけません。今年の母の日は5月9日(日)で父の日は6月20日(日)ですよ。準備はできていますか?

以上が while の中の処理になりますが、この処理の最中に親プロセスが SIGTERM を捕捉していなければ上記の処理を繰り返します。もし、親プロセスが SIGTERM を捕捉していれば while を抜け終了しますが SIGHUP を捕捉した場合は子プロセスを SIGTERM で殺して親プロセス自体は 19行目の while に戻ります。任意のシグナルを捕捉した場合に親プロセスが終了するかどうかは while をどのような条件で回すかによりますが、そもそも trap_signals で指定していないシグナルを捕捉した場合はデフォルトの挙動を取ります。例では SIGINT (Ctrl+C) を親プロセスに送ると直ちにプログラムが終了します。

以上がサンプルコードの説明になります。
このサンプルコードは github のリポジトリにも置いてあるので興味のある方は奥一穂さんのすばらしいライブラリと日頃見守ってくれている親に感謝しつつ実際に動かしてみてください。