【问题标题】:PHP forking and multiple child signalsPHP 分叉和多个子信号
【发布时间】:2011-01-17 06:50:59
【问题描述】:

我正在尝试编写一个使用pcntl_* functions 创建多个分叉子进程的脚本。

基本上,有一个脚本循环运行大约一分钟,定期轮询数据库以查看是否有要运行的任务。如果有的话,它应该在一个单独的进程中派生和运行任务,这样父进程就不会被长时间运行的任务拖住。

由于可能有大量任务准备运行,我想限制创建的子进程的数量。因此,我通过在每次创建一个变量时增加一个变量来跟踪进程的数量(如果有太多则暂停),然后在信号处理程序中减少它。有点像这样:

define(ticks = 1);

$openProcesses = 0; // how many we have open
$max = 3;           // the most we want open at a time

pcntl_signal(SIGCHLD, "childFinished");

while (!time_is_up()) {
    if (there_is_something_to_do()) {
        $pid = pcntl_fork();
        if (!$pid) {      // I am the child
            foo();        //   run the long-running task
            exit(0);      //   and exit
        } else {          // I am the parent
            ++$openProcesses;
            if ($openProcesses >= $max) {
                pcntl_wait($status);    // wait for any child to exit 
            }                           // before continuing
        }
    } else {
        sleep(3);
    }
}

function childFinished($signo) {
    global $openProcesses;
    --$openProcesses;
}

这在大多数情况下都可以正常工作,除非两个或多个进程同时完成 - 信号处理函数只调用一次,这会抛出我的计数器。原因由notes of the PHP manual中的“匿名”解释:

返回的多个子节点少于在给定时刻退出的子节点数量 SIGCHLD 信号是 Unix (POSIX) 系统的正常行为。 SIGCHLD 可能被解读为“一个或多个孩子改变了状态——去检查你的孩子并收获他们的状态值”。

我的问题是:我如何检查子进程并获取他们的状态?有没有可靠的方法来检查在任何给定时间打开了多少子进程?

使用 PHP 5.2.9

【问题讨论】:

  • 可能只使用rabbitmq.com 会使整个事情更不容易出错

标签: php process fork


【解决方案1】:

您可以让孩子在他们开始时向父母发送一个 SIGUSR1,然后在他们退出之前发送一个 SIGUSR2。使用原始信号时要处理的另一件事是合并它们的内核,它不适用于 RT 信号。理论上,任何非 rt 信号都可以合并。

您可以使用 sqlite 实现某种简单的锁定,其中一次只有一个孩子可以使用说话棒。只需确保孩子们处理通常致命的信号,以便他们保持活力以释放锁。

【讨论】:

    【解决方案2】:

    一种方法是保留子进程的 PID 数组,并在信号处理程序中检查每个 PID 以查看它是否仍在运行。 (未经测试的)代码如下所示:

    declare(ticks = 1);
    
    $openProcesses = 0; 
    $procs = array();
    $max = 3;
    
    pcntl_signal(SIGCHLD, "childFinished");
    
    while (!time_is_up()) {
        if (there_is_something_to_do()) {
            $pid = pcntl_fork();
            if (!$pid) {      
                foo();        
                exit(0);      
            } else {          
    
                $procs[] = $pid; // add the PID to the list
    
                ++$openProcesses;
                if ($openProcesses >= $max) {
                    pcntl_wait($status);     
                }                           
            }
        } else {
            sleep(3);
        }
    }
    
    function childFinished($signo) {
    
        global $openProcesses, $procs;
    
        // Check each process to see if it's still running
        // If not, remove it and decrement the count
        foreach ($procs as $key => $pid) if (posix_getpgid($pid) === false) {
            unset($procs[$key]);
            $openProcesses--;
        }
    
    }
    

    【讨论】:

      【解决方案3】:

      我知道这已经晚了大约 8 年(我希望你能找到答案),但以防万一它对其他人有帮助,我将回答。

      使用 pcntl_w* 函数将是您的朋友,您可能想要实现一个进程收割器。该文档不是很有帮助,仍然没有包含任何有用的示例。

      这将是一个多部分的过程:

      1 - 使用 pcntl_signal 将捕获的信号发送到您的信号处理程序

      2 - 在该循环内进行循环/轮询;

      3 - 遍历您的孩子的数组(您将在下面创建)并根据需要获取它们

      4 - fork():这将包括以下内容:

      pcntl_async_signals(true);
      
      $children = array();
      while ($looping === true)
      {
          reapChildren();
          if (($pid = pcntl_fork()) exit (1); // error
          elseif ($pid) // parent
          { 
              $children[] = $pid;
              // close files/sockets/etc
              posix_setpgid ($pid,posix_getpgrp());
          }
          else
          { // child
              posix_setpgid(posix_getpid(),posix_getppid());
              // ... jump to child function/object/code/etc ...
              exit (0); // or whatever code you want to return
          }
      } // end of loop
      

      在收割者中,您将需要以下内容:

      function reapChildren()
      {
          global $children;
          foreach ($children as $idx => $pid)
          {
              $rUsage = array();
              $status = 0; // integer which will be used as the $status pointer
              $ret = pcntl_waitpid($pid, $status, WNOHANG|WUNTRACED, $rUsage);
              if (pcntl_wifexited($status)) // the child exited normally
              {
                  $exitCode = pcntl_wexitstatus($status); // returns the child exit status
              }
              if (pcntl_wifsignaled($status))  // the child received a signal
              {
                  $signal = pcntl_wtermsig($status); // returns the signal that abended the child
              }
              if (pcntl_wifstopped($status))
              {
                  $signal = pcntl_wstopsig($status); // returns the signal that  stopped the child
              }
          }
      }
      

      上面的 reaper 代码将允许您轮询您孩子的状态,如果您使用的是 php7+,则在您的信号处理程序中填写的 $signalInfo 数组将包含许多您可以使用的有用信息.. var_dump it .. 看看这个。另外,在 php7+ 中使用 pcntl_async_signals(true) 代替了 declare(ticks=1) 和手动调用 pcntl_signal_dispatch();

      我希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 2012-09-23
        • 2014-06-25
        • 1970-01-01
        • 1970-01-01
        • 2023-04-04
        • 2011-06-17
        • 2015-08-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多