Monday, January 31, 2011

Process Forking with PHP background

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.


http://www.electrictoolbox.com/article/php/process-forking/

No comments: