PCNTL関数を使用したプロセス制御でマルチプロセス(?)を
実現するサンプルです。
参考にさせていただいたサイト(コード)
実装結果
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()を繰り返すと残念な事態になります。(なりました)
exec_multi_process_sample.php
<?php
require_once('./processctrl.class.php');
date_default_timezone_set('Asia/Tokyo');
$pctrl = Processctrl::getInstace();
// 【その1】
// 同時プロセス数は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'));
});
// 【その2】
// 同時プロセス数は5に設定して、タイムアウト時間を6に設定
// 処理内容はsleep(5)を実行するだけ(∴タイムアウトしない)
// ※clearArgs()していないので「その1」で設定した引数で実行される
$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】
// 同時プロセス数は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!! <<<<