PCNTL関数を使用したプロセス制御でマルチプロセス(？)を実現するサンプル


PCNTL関数を使用したプロセス制御でマルチプロセス(？)を
実現するサンプルです。

## 参考にさせていただいたサイト(コード)

* [PHPでpcntl_forkでふぉーくの薀蓄](http://rhythm-prism.blogspot.jp/2011/04/phppcntlfork.html)
* [PHPでの並列処理についてはてなブックマーク - PHPでの並列処理について](http://blog.satooshi.jp/blog/2009/11/16/multi-processing-in-php/)
* [PCNTL関数](http://jp2.php.net/manual/ja/ref.pcntl.php)

## 実装結果

```php:processctrl.class.php
<?php
declare(ticks = 1);

class Processctrl {

    protected $_max_process = 3;

    protected $_timeout = 5;

    protected $_args;

    protected $_stack;

    static public function getInstace()
    {
        static $object;
        if (is_null($object)) {
            $object = new static;
        }

        return $object;
    }

    public function __construct()
    {
        $this->_args = array();
        $this->_stack = array();

        pcntl_signal(SIGALRM, function ($signal) {
            //Log::debug("Get signal [".$signal."]");
            switch ($signal) {
                case SIGALRM:
                    echo ">>>> TIMEOUT!! <<<<\n";
                    exit();
                    break;
                default:
                    break;
            }
        });
    }

    public static function setMaxProcess($max_process)
    {
        static::getInstace()->_max_process = $max_process;
    }

    public static function setTimeout($sec)
    {
        static::getInstace()->_timeout = $sec;
    }

    public function addArgs($args)
    {
        $this->_args[] = $args;
    }

    public function clearArgs()
    {
        $this->_args = array();
    }

    public function run_all(callable $callback)
    {
        if (!is_callable($callback)) {
            throw new \Exception('Not callable['.$callback.']');
        }

        // 子プロセスを生成し処理を実行する
        foreach ($this->_args as $args) {
            $pid = pcntl_fork();
            if (-1 === $pid) {
                throw new \Exception('False fork process ['.pcntl_get_last_error().']');
            }

            if ($pid) {
                //Log::debug("[" . __METHOD__ . "]: Parent process PID[".$pid."].");
                $this->_stack[$pid] = true;
                if (count($this->_stack) >= $this->_max_process) {
                    //Log::debug("[" . __METHOD__ . "]: Stacked process is max ...waiting...[".count($this->_stack)."].");
                    unset($this->_stack[pcntl_waitpid(-1, $status, WUNTRACED)]);
                }
            } else {
                //Log::debug("[" . __METHOD__ . "]: Child process running.");
                pcntl_alarm($this->_timeout);
                call_user_func_array($callback, $args);
                exit();
            }
        }

        // すべての子プロセスの終了を待つ
        while (count($this->_stack) > 0) {
            //Log::debug("[" . __METHOD__ . "]: Waiting process all end...[".count($this->_stack)."].");
            unset($this->_stack[pcntl_waitpid(-1, $status, WUNTRACED)]);
        }
    }
}
```

>【補足】
>declare(ticks = 1);
>↑pcntl_signalを使用するために必要な記述。(ticksとか説明読んでもよくわかりません)
>http://www.php.net/manual/ja/intro.pcntl.php
>『…PCNTLはシグナルハンドルコールバックの仕組みとしてticksを使用しており…』

## 使用テスト（※PHP 5.4.11で実行しています）

__尚、clearArgs()を忘れてaddArgs()とrun_all()を繰り返すと残念な事態になります。(なりました)__

```php:exec_multi_process_sample.php
<?php

require_once('./processctrl.class.php');

date_default_timezone_set('Asia/Tokyo');

$pctrl = Processctrl::getInstace();

// 【その１】
// 同時プロセス数は3に設定して
// 処理内容はsleep(2)を実行するだけ
$pctrl->setMaxProcess(3);

$pctrl->addArgs(array(1));
$pctrl->addArgs(array(2));
$pctrl->addArgs(array(3));
$pctrl->addArgs(array(4));
$pctrl->addArgs(array(5));
$pctrl->addArgs(array(6));
$pctrl->addArgs(array(7));
$pctrl->addArgs(array(8));

echo ">>> Run-1\n";
$pctrl->run_all(function($num) {
	$date = new DateTime("now");
	printf("$num SLEEP(02)[%s]\n", $date->format('Y-m-d H:i:s'));
	sleep(2);
	$date = new DateTime("now");
	printf("$num END[%s]\n", $date->format('Y-m-d H:i:s'));
});

// 【その２】
// 同時プロセス数は5に設定して、タイムアウト時間を6に設定
// 処理内容はsleep(5)を実行するだけ(∴タイムアウトしない)
// ※clearArgs()していないので「その１」で設定した引数で実行される

$pctrl = Processctrl::getInstace();
$pctrl->setMaxProcess(5);
$pctrl->setTimeout(6);

echo ">>> Run-2\n";
$pctrl->run_all(function($num) {
	$date = new DateTime("now");
	printf("$num SLEEP(05)[%s]\n", $date->format('Y-m-d H:i:s'));
	sleep(5);
	$date = new DateTime("now");
	printf("$num END[%s]\n", $date->format('Y-m-d H:i:s'));
});

// 【その２】
// 同時プロセス数は2に設定して、タイムアウト時間を5に設定
// 処理内容はsleep(10)を実行するだけ(∴タイムアウトする)
// ※clearArgs()して新たに引数をセット

$pctrl->clearArgs();
$pctrl->setMaxProcess(2);
$pctrl->setTimeout(5);

$pctrl->addArgs(array(1));
$pctrl->addArgs(array(2));
$pctrl->addArgs(array(3));
$pctrl->addArgs(array(4));

echo ">>> Run-3\n";
$pctrl->run_all(function($num) {
	$date = new DateTime("now");
	printf("$num SLEEP(10)[%s]\n", $date->format('Y-m-d H:i:s'));
	sleep(10);
	$date = new DateTime("now");
	printf("$num END[%s]\n", $date->format('Y-m-d H:i:s'));
});
```

```:実行結果
>>> Run-1
1 SLEEP(02)[2013-05-06 17:37:21]
2 SLEEP(02)[2013-05-06 17:37:21]
3 SLEEP(02)[2013-05-06 17:37:21]
1 END[2013-05-06 17:37:23]
2 END[2013-05-06 17:37:23]
3 END[2013-05-06 17:37:23]
4 SLEEP(02)[2013-05-06 17:37:23]
5 SLEEP(02)[2013-05-06 17:37:23]
6 SLEEP(02)[2013-05-06 17:37:23]
4 END[2013-05-06 17:37:25]
5 END[2013-05-06 17:37:25]
6 END[2013-05-06 17:37:25]
7 SLEEP(02)[2013-05-06 17:37:25]
8 SLEEP(02)[2013-05-06 17:37:25]
7 END[2013-05-06 17:37:27]
8 END[2013-05-06 17:37:27]

>>> Run-2
1 SLEEP(05)[2013-05-06 17:37:27]
2 SLEEP(05)[2013-05-06 17:37:27]
3 SLEEP(05)[2013-05-06 17:37:27]
4 SLEEP(05)[2013-05-06 17:37:27]
5 SLEEP(05)[2013-05-06 17:37:27]
1 END[2013-05-06 17:37:32]
2 END[2013-05-06 17:37:32]
3 END[2013-05-06 17:37:32]
4 END[2013-05-06 17:37:32]
5 END[2013-05-06 17:37:32]
6 SLEEP(05)[2013-05-06 17:37:32]
7 SLEEP(05)[2013-05-06 17:37:32]
8 SLEEP(05)[2013-05-06 17:37:32]
6 END[2013-05-06 17:37:37]
7 END[2013-05-06 17:37:37]
8 END[2013-05-06 17:37:37]

>>> Run-3
1 SLEEP(10)[2013-05-06 17:37:37]
2 SLEEP(10)[2013-05-06 17:37:37]
1 END[2013-05-06 17:37:42]
>>>> TIMEOUT!! <<<<
2 END[2013-05-06 17:37:42]
>>>> TIMEOUT!! <<<<
3 SLEEP(10)[2013-05-06 17:37:42]
4 SLEEP(10)[2013-05-06 17:37:42]
3 END[2013-05-06 17:37:47]
>>>> TIMEOUT!! <<<<
4 END[2013-05-06 17:37:47]
>>>> TIMEOUT!! <<<<
```