2

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

我知道 <defunct> 进程并不邪恶,但我无法弄清楚为什么它只在从 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辅助| 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 运行时会出现这个错误?

4

1 回答 1

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;
}
于 2013-05-01T22:23:41.640 回答