在某些情况下,使用 pcntl_fork() 可能有点棘手。对于快速作业,子进程可能在父进程执行与启动进程相关的某些代码之前完成处理。父进程可能会在准备好处理子进程状态之前收到信号。为了处理这种情况,我在信号处理程序中向进程“队列”添加一个 ID,如果父进程尚未准备好处理它们,则需要清理这些 ID。
<?php
declare(ticks=1);
class JobDaemon{
public $maxProcesses = 25;
protected $jobsStarted = 0;
protected $currentJobs = array();
protected $signalQueue=array();
protected $parentPID;
public function __construct(){
echo "已构造 \n";
$this->parentPID = getmypid();
pcntl_signal(SIGCHLD, array($this, "childSignalHandler"));
}
public function run(){
echo "正在运行 \n";
for($i=0; $i<10000; $i++){
$jobID = rand(0,10000000000000);
$launched = $this->launchJob($jobID);
}
while(count($this->currentJobs)){
echo "等待当前任务完成... \n";
sleep(1);
}
}
protected function launchJob($jobID){
$pid = pcntl_fork();
if($pid == -1){
error_log('无法启动新任务,退出');
return false;
}
else if ($pid){
$this->currentJobs[$pid] = $jobID;
if(isset($this->signalQueue[$pid])){
echo "在信号队列中找到 "$pid", 现在正在处理它 \n";
$this->childSignalHandler(SIGCHLD, $pid, $this->signalQueue[$pid]);
unset($this->signalQueue[$pid]);
}
}
else{
$exitStatus = 0; echo "在 pid ".getmypid()." 中执行一些有趣的操作\n";
exit($exitStatus);
}
return true;
}
public function childSignalHandler($signo, $pid=null, $status=null){
if(!$pid){
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}
while($pid > 0){
if($pid && isset($this->currentJobs[$pid])){
$exitCode = pcntl_wexitstatus($status);
if($exitCode != 0){
echo "$pid 退出,状态为 ".$exitCode."\n";
}
unset($this->currentJobs[$pid]);
}
else if($pid){
echo "..... 将 "$pid" 添加到信号队列 ..... \n";
$this->signalQueue[$pid] = $status;
}
$pid = pcntl_waitpid(-1, $status, WNOHANG);
}
return true;
}
}