Monday, June 14, 2010

[Backup for peronal reference]

Process Forking with PHP

Posted March 4th, 2004 in PHP

Unix supports the process of forking which allows a parent process to spawn a subprocess and continue running both processes concurrently. It is possible to do this in PHP using the PHP Process Control Functions. Note that you should never attempt to use these process control forking functions when using a webserver; you shuld only fork applications when using the PHP command line client.

Before you can use the PHP process control functions you must compile the PCNTL extensions into PHP using the --enable-pcntl configure option (ie ./configure --enable-pcntl along with all the other configuration options you would like to compile into the PHP binary). No additional libraries need to be pre-installed. Note that these process control extensions will not work on non-Unix platforms (ie Microsoft Windows).

Basic Forking Example

A very basic and commonly used example for forking a process in PHP is as follows:

$pid = pcntl_fork();

if($pid) {
// parent process runs what is here
print "parent\n";
}
else {
// child process runs what is here
print "child\n";
}

Running this will output the following:

child
parent

Child process is a copy of the parent process

What actually happens when you call the pcntl_fork() function is that a child process is spawned which is exactly the same as the parent process and continues processing from the line below the function call. All variables and objects etc are copied into the child process as-is but these are new copies which belong to the new process. Modifying them in the child process does not affect the values in the parent (or any other forked) process.

The parent process will have a value assigned to $pid whereas the child process will not, hence the if test. Note that in the above example, both processes would continue running whatever code is after the if statement, something which is rarely mentioned in examples of PHP process forking on the web.

To illustrate this, we'll modify the example above slightly to add additional output as follows:

$pid = pcntl_fork();

print "start\n";

if($pid) {
// parent process runs what is here
print "parent\n";
}
else {
// child process runs what is here
print "child\n";
}

print "end\n";

Running this will display the following:

start
child
end
start
parent
end

Making the parent process wait until the child has finished

So ideally you want to let either the child or parent continue processing the rest of the script and make the other process exit after the process is forked. If you exit from the parent process, however, you'll end up with "zombie" processes running which do not belong to any process. Therefore the parent process needs to wait until all the child processes have finished running before exiting itself. You can do this using the pcntl_waitpid() function, which will cause the parent process to wait until the child process has completed. You can then either just let the parent process exit, or do any tidy up code that is required.

An example of doing this is as follows:

$pid = pcntl_fork();

if($pid) {
// this is the parent process
// wait until the child has finished processing then end the script
pcntl_waitpid($pid, $status, WUNTRACED);
exit;
}

// the child process runs its stuff here and then ends

...

The exit call in the parent process ensures that processing stops at that point and the parent does not execute any of the code intended for the child. Another way of doing the same thing without the exit code would be as follows:

$pid = pcntl_fork();

if($pid) {
// this is the parent process
// wait until the child has finished processing then end the script
pcntl_waitpid($pid, $status, WUNTRACED);
}
else {
// the child process runs its stuff here
}

Exit codes from the child process

You could optionally have an exit call at the end of the child part of the if statement.

The $status parameter passed to pcntl_waitpid() stores the return value from the child process. If the child process returns 0 (ie success) then it will also be zero. On my Linux desktop the value returned as $status would be the value returned from the exit call multipled by 256. So if the child process ended with exit(2) my system returned 512 as the $status value. Whether this is the same across all Unix systems I do not know.

Getting the parent process to wait until the child process has completed is useful for then doing something else based on the return value of the child process as shown in the following example:

$pid = pcntl_fork();

if($pid) {
// this is the parent process
// wait until the child has finished processing then end the script
pcntl_waitpid($pid, $status, WUNTRACED);
if($status > 0) {
// an error occurred so do some processing to deal with it
}
}
else {
// the child process runs its stuff here
...
if(...successful condition...) {
exit(0); // this indicates success
}
else {
exit(1); // this indicates failure
}
}

Forking multiple child processes

This final example illustrates how you could fork several children from the parent process with PHP. The loop runs three times and forks a child process for each loop, storing the child pids into an array. After running the stuff for the child process the child then exits. A second loop runs in the parent after the first to ensure all child processes have finished running before resuming its own process.

Note it is very important in this sort of process that the child explicitly exits in its section of the script, otherwise each child will continue running through the first, and then second, loop.

$pids = array();

for($i = 0; $i < 3; $i++) {

$pids[$i] = pcntl_fork();

if(!$pids[$i]) {
// child process
...
exit();
}

}

for($i = 0; $i < 3; $i++) {
pcntl_waitpid($pids[$i], $status, WUNTRACED);
}

// complete parent processing now all children have finished
...

The PHP manual pages for process control functions can be found at www.php.net/manual/en/ref.pcntl.php. There are a number of user contributed notes for each of the functions which should also help with your understanding of process forking in PHP.

No comments: