我正在尝试编写一个脚本,该脚本使用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
答案 0 :(得分:2)
一种方法是保留子进程的PID数组,并在信号处理程序中检查每个PID以查看它是否仍在运行。 (未经测试的)代码如下所示:
define(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--;
}
}
答案 1 :(得分:0)
您可以让孩子在启动时向父母发送SIGUSR1,然后在退出之前发送SIGUSR2。使用原始信号时要处理的另一件事是内核合并它们,它与RT信号无关。从理论上讲,任何非rt信号都可以合并。
您可以使用sqlite实现某种简单的锁定,其中一次只有一个孩子可以使用说话棒。只要确保孩子们正常处理致命的信号,这样他们就可以保持活着以释放锁定。
答案 2 :(得分:0)
我知道这已经晚了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
}
}
}
上面的收割者代码将允许您轮询孩子的状态,如果您使用的是php7 +,则在信号处理程序中填充的$ signalInfo数组将包含许多可以使用的有用信息。var_dump .. 一探究竟。另外,在php7 +中使用pcntl_async_signals(true)取代了对clarify(ticks = 1)的需要,并手动调用pcntl_signal_dispatch();
我希望这会有所帮助。