PHPで並列処理

PHP で並列処理 - 個人的なメモと備忘録より。
ちょいといじくって『出来てる』気分に浸ってみた。実際のforkとかシグナルとかは曖昧なまま。

今の仕事で『GETクエリを変更して作った複数のURLに対して、複数クライアントからの同時リクエスト』についてのプチストレステストをする事になった。で、abで『異なるURL』に対してリクエストする事が出来るか分からないし、他のツールも思いつかない。探すのも面倒だし、あまり時間もないしという事でスクリプト書いた方が早いかなと思って家で調査。

きっと、RubyとかPerlとかだと普通にマルチスレッド使えて分かり易いんだろうけど、テスト対象のシステムがPHPで書かれてるし、そのリポジトリには出来るだけPHPコードを入れた方がいい気がしたのでPHPで調査。参考にした記事が2003年に書かれたものだったので、PHP5系ではこの方法じゃなくても出来るのかも。けど、動いたし問題無さげだからこれでよし。

今日書いたコードをテスト用に修正して明日実行予定。『複数URLの同時リクエスト』って本当はどうやるんだろう?

test[1-3].php
<?php echo __FILE__ . "\n"; ?>
multi_request.php
<?php

// 同時に起動する子プロセスの最大数(default)
define('MAX_PROCESS_NUM', 6);

class MultiRequest {

    private $urls;
    private $maxProcessNum;

    /**
     * コンストラクタ
     *
     * @param Array   $urls          リクエストURLの配列
     * @param Integer $maxProcessNum 同時に起動する子プロセスの最大数
     */
    public function __construct($urls, $maxProcessNum = MAX_PROCESS_NUM) {
        declare(ticks = 1);
        $this->urls          = $urls;
        $this->maxProcessNum = $maxProcessNum;

        pcntl_signal(SIGTERM, array($this, 'default_sig_handler'));
        pcntl_signal(SIGHUP,  array($this, 'default_sig_handler'));
        pcntl_signal(SIGUSR1, array($this, 'default_sig_handler'));

        $this->processWork = array($this, 'defaultWork');
    }

    /**
     * シグナルハンドラを設定
     *
     * @param Integer $signal          シグナル
     * @param Mixed   $handler         コールバック
     * @param Boolean $restart_syscall 再起動のシステムコールへの対応
     */
    public function setSignalHandler($signal, $handler, $restart_syscall = true) {
        return pcntl_signal($signal, $handler, $restart_syscall);
    }

    /**
     * デフォルトのシグナルハンドラ
     *
     * @param Integer $signo シグナル
     */
    private function default_sig_handler($signo) {
        switch ($signo) {
        case SIGTERM:
            echo "shutdown...\n";
            exit;
            break;
        case SIGHUP:
            echo "reboot...\n";
            break;
        case SIGUSR1:
            echo "SIGUSER($signo)\n";
            break;
        default:
            echo "Other signal: " . $signo . "\n";
        }
    }

    public function setProcessWork($callback) {
        $this->processWork = $callback;
    }

    private function defaultWork($url) {
        $data = file_get_contents($url);
        // ... 取得したデータの処理 ...
        echo "[" . time() . "]" . $data . "\n";
        sleep(1);
    }

    /**
     * 実行
     */
    public function run() {
        foreach ($this->urls as $url) {
            $pid = pcntl_fork();
            if ($pid == -1) {
                throw new Exception('Failed forc process.');
            } else if ($pid) {
                // 親プロセス
                $pids[$pid] = TRUE;
                if (count($pids) >= $this->maxProcessNum) {
                    unset($pids[pcntl_waitpid(-1, $status, WUNTRACED)]);
                }
            } else {
                // $url を使用して子プロセスで行う処理

                // 120 秒後に強制終了
                pcntl_alarm(120);
                // 子プロセスで行う仕事
                if (is_array($this->processWork)) {
                    $obj  = $this->processWork[0];
                    $func = $this->processWork[1];
                    $obj->$func($url);
                } else {
                    $function = $this->processWork;
                    $function($url);
                }
                // 子プロセスを終了
                exit;
            }
        }
        // 全ての処理が終了するまで待機
        while (count($pids) > 0) {
            unset($pids[pcntl_waitpid(-1, $status, WUNTRACED)]);
        }
    }

}

$urls = array(
              'http://localhost/apache2-default/test1.php',
              'http://localhost/apache2-default/test2.php',
              'http://localhost/apache2-default/test3.php',
              'http://localhost/apache2-default/test1.php',
              'http://localhost/apache2-default/test2.php',
              'http://localhost/apache2-default/test3.php',
              'http://localhost/apache2-default/test1.php',
              'http://localhost/apache2-default/test2.php',
              'http://localhost/apache2-default/test3.php',
              );

$multiRequest = new MultiRequest($urls);
echo "Default process work:\n";
$multiRequest->run();
echo "Override process work:\n";
$multiRequest->setProcessWork(create_function('$url', '$data = file_get_contents($url); echo "/" . time() . "/ $data\n"; sleep(1);'));
$multiRequest->run();

/** RESULT
Default process work:
[1153837887]/var/www/apache2-default/test1.php
[1153837887]/var/www/apache2-default/test2.php
[1153837887]/var/www/apache2-default/test3.php
[1153837887]/var/www/apache2-default/test1.php
[1153837887]/var/www/apache2-default/test2.php
[1153837887]/var/www/apache2-default/test3.php
[1153837889]/var/www/apache2-default/test1.php
[1153837889]/var/www/apache2-default/test2.php
[1153837889]/var/www/apache2-default/test3.php
Override process work:
/1153837890/ /var/www/apache2-default/test1.php
/1153837890/ /var/www/apache2-default/test2.php
/1153837890/ /var/www/apache2-default/test3.php
/1153837890/ /var/www/apache2-default/test1.php
/1153837890/ /var/www/apache2-default/test2.php
/1153837890/ /var/www/apache2-default/test3.php
/1153837891/ /var/www/apache2-default/test1.php
/1153837891/ /var/www/apache2-default/test2.php
/1153837891/ /var/www/apache2-default/test3.php
 */

?>
プロフィール

このブログ記事について

このページは、koshigoeが2006年7月25日 23:44に書いたブログ記事です。

ひとつ前のブログ記事は「文字列の近似はPHPで用意されてたらしい」です。

次のブログ記事は「MTのスタイル変えてみた」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。