LoginSignup
31
29

More than 5 years have passed since last update.

PHP で複数のプロセスを並列して起動

Posted at

React の真似事です。

PHP にスレッドはありませんが、stream_select()stream_set_blocking() を使用することで、IO を多重化させ、並列処理を行うことができます。

初めに 1 秒置きに標準出力に出力し、最後に標準エラー出力に出力するだけの簡単なプログラムを用意します。
引数に渡した数値の数だけループします。

slow.rb
ARGV[0].to_i.times do |n|
  STDOUT.puts n
  STDOUT.flush
  sleep 1
end
STDERR.puts "Bye"
STDERR.flush

次にこれを並列に起動するプログラムを PHP で実装します。
かなり汚いですが。

parallel_proc.php
<?php
class ProcLoop
{
    private $procs = array();
    private $pipes = array();
    private $stdoutCallbacks = array();
    private $stderrCallbacks = array();
    private static $fdSpecs = array(
        1 => array('pipe', 'w'),
        2 => array('pipe', 'w'),
    );

    public function addProc($cmd, $stdoutCallback, $stderrCallback)
    {
        $this->procs[] = proc_open($cmd, static::$fdSpecs, $pipes);
        stream_set_blocking($pipes[1], 0);
        stream_set_blocking($pipes[2], 0);
        $this->pipes[] = array($pipes[1], $pipes[2]);
        $this->stdoutCallbacks[] = $stdoutCallback;
        $this->stderrCallbacks[] = $stderrCallback;
    }

    public function run()
    {
        $count = count($this->procs);
        while ($this->isAnyPipeAvailable()) {
            for ($i = 0; $i < $count; $i++) {
                $ret = stream_select($tmp = $this->pipes[$i], $write = NULL, $except = NULL, 1);
                if ($ret === false) {
                    throw new RuntimeException;
                } else if ($ret !== 0) {
                    foreach ($this->pipes[$i] as $sock) {
                        if ($buf = fread($sock, 4096)) {
                            if ($buf) {
                                if ($sock === $this->pipes[$i][0]) {
                                    call_user_func($this->stdoutCallbacks[$i], $buf);
                                } else {
                                    call_user_func($this->stderrCallbacks[$i], $buf);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private function isAnyPipeAvailable()
    {
        foreach ($this->pipes as $pipe) {
            if (feof($pipe[0]) === false || feof($pipe[1]) === false) {
                return true;
            }
        }
        return false;
    }
}

$loop = new ProcLoop;

$echoStdout = function ($buf) {
    echo "\033[34m[STDOUT]\033[0m " . $buf;
};

$echoStderr = function ($buf) {
    echo "\033[31m[STDERR]\033[0m " . $buf;
};

$loop->addProc('ruby slow.rb 3', $echoStdout, $echoStderr);
$loop->addProc('ruby slow.rb 6', $echoStdout, $echoStderr);
$loop->addProc('ruby slow.rb 9', $echoStdout, $echoStderr);
$loop->run();

これを実行すると、以下のような出力が得られます。

3 つのプロセスが同時に起動し、起動時間の短いものから順番に終了していることがわかります。
全てのプロセスが終了した時点 (というかパイプが終端に到達した時点) でこの PHP スクリプトも終了します。

React でこういうのをやる場合、どんな書き方をするのがキレイなのか知りたい (というかまだ用意されていない?)

See also

php-uv

31
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
29