pcntl_fork()导致已失效的父进程

时间:2013-04-26 14:06:52

标签: php pcntl defunct

所以,我有这个监听IPC消息的PHP守护进程工作者。 奇怪的是父进程(来自pcnt​​l_fork的结果)留下了[php]<被禁>进程,直到子进程完成,但仅当脚本从cronjob启动时,而不是直接从命令行启动。

我知道<被禁>进程并不邪恶,但我无法弄清楚为什么只有在从cronjob运行时它才会发生。

命令

/path/to/php/binary/php /path/to/php/file/IpcServer.php

分叉代码:

$iParent = posix_getpid();
$iChild  = pcntl_fork();

if ($iChild == -1)
    throw new Exception("Unable to fork into child process.");

elseif ($iChild)
{
    echo "Forking into background [{$iChild}].\n";

    Log::d('Killing parent process (' . $iParent. ').');
    exit;
}

输出

Forking into background [20835].
Killing parent process (20834).

ps aux | grep php

root 20834 0.0 0.0 0 0 ? Zs 14:28 0:00 [php] <defunct>
root 20835 0.0 0.2 275620 8064 ? Ss 15:35 0:00 /path/to/php/binary/php /path/to/php/file/IpcServer.php

我发现这是一个已知的apache错误,但是为什么我从cronjob运行时会遇到这个错误?

1 个答案:

答案 0 :(得分:4)

在PHP中,除非父母等待pcntl_wait()pcntl_waitpid()返回,否则孩子将成为僵尸进程。一旦所有进程结束或处理,僵尸将被销毁。如果没有处理孩子并且孩子的跑步时间比父母长,那么看起来父母也会变成僵尸。

来自pcntl_fork页面的示例:

$pid = pcntl_fork();
if ($pid == -1) {
     die('could not fork');
} else if ($pid) {
     // we are the parent
     pcntl_wait($status); //Protect against Zombie children
} else {
     // we are the child
}

或者像这样使用信号处理来防止在主线程上等待:

$processes = array(); // List of running processes
$signal_queue = array(); // List of signals for main thread read
// register signal handler
pcntl_signal(SIGCHLD, 'childSignalHandler');

// fork. Can loop around this for lots of children too.
switch ($pid = pcntl_fork()) {
    case -1: // FAILED
        break;
    case 0: // CHILD
        break;
    default: // PARENT
        // ID the process. Auto Increment or whatever unique thing you want
        $processes[$pid] = $someID;
        if(isset($signal_queue[$pid])){
            childSignalHandler(SIGCHLD, $pid, $signal_queue[$pid]);
            unset($signal_queue[$pid]);
        }
        break;
}

function childSignalHandler($signo, $pid=null, $status=null){
    global $processes, $signal_queue;
    // If no pid is provided, Let's wait to figure out which child process ended
    if(!$pid){
        $pid = pcntl_waitpid(-1, $status, WNOHANG);
    }

    // Get all exited children
    while($pid > 0){
        if($pid && isset($processes[$pid])){
            // I don't care about exit status right now.
            //  $exitCode = pcntl_wexitstatus($status);
            //  if($exitCode != 0){
            //      echo "$pid exited with status ".$exitCode."\n";
            //  }
            // Process is finished, so remove it from the list.
            unset($processes[$pid]);
        }
        else if($pid){
            // Job finished before the parent process could record it as launched.
            // Store it to handle when the parent process is ready
            $signal_queue[$pid] = $status;
        }
        $pid = pcntl_waitpid(-1, $status, WNOHANG);
    }
    return true;
}