I'm trying to write a script which creates a number of forked child processes using the pcntl_*
functions.
Basically, there is a single script which runs in a loop for about a minute, periodically polling a database to see if there is a task to be run. If there is one, it should fork and run the task in a separate process so that the parent isn't held up by a long-running task.
Since there possibly could be a large number of tasks ready to be run, I want to limit the number of child processes that are created. Therefore, I am keeping track of the number of processes by incrementing a variable each time one is created (and then pausing if there's too many), and then decrementing it in a signal handler. Kind of like this:
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;
}
This works pretty much ok most of the time, except for when two or more processes finish simultaneously - the signal handler function is only called once, which throws out my counter. The reason for this is explained by "Anonymous" in the notes of the PHP manual:
Multiple children return less than the number of children exiting at a given moment SIGCHLD signals is normal behavior for Unix (POSIX) systems. SIGCHLD might be read as "one or more children changed status -- go examine your children and harvest their status values".
My question is this: How do I examine the children and harvest their status? Is there any reliable way to check how many child processes are open at any given time?
Using PHP 5.2.9
One way is to keep an array of the PIDs of the child processes, and in the signal handler check each PID to see if it's still running. The (untested) code would look like:
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--;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With