Implicit Evaluation with PHP

1 May 2007

Multi-threading strategies in PHP

PHP does not have threading anywhere in its massive core. We can, however, fake it by relying on the underlying operating system’s multitasking abilities instead of PHP. This article will show you how.

PHP has no built in support for threading. But there can still be times when you’ve got lengthy code to run and idle CPU cyles you’d like to capitalize on. We can treat child processes as threads.

I created a PHP4-compatible class named Thread to abstract away the details of process management. That class follows:

class Thread {
var $pref ; // process reference
var $pipes; // stdio
var $buffer; // output buffer
/* private */ function Thread() {
$this->pref = 0;
$this->buffer = "";
$this->pipes = (array)NULL;
}
/* public static */ function Create ($file) {
$t = new Thread;
$descriptor = array (0 => array ("pipe", "r"), 1 => array ("pipe", "w"), 2 => array ("pipe", "w"));
$t->pref = proc_open ("php -q $file ", $descriptor, $t->pipes);
stream_set_blocking ($t->pipes[1], 0);
return $t;
}
/* public instance */ function isActive () {
$this->buffer .= $this->listen();
$f = stream_get_meta_data ($this->pipes[1]);
return !$f["eof"];
}
/* public instance */ function close () {
$r = proc_close ($this->pref);
$this->pref = NULL;
return $r;
}
/* public instance */ function tell ($thought) {
fwrite ($this->pipes[0], $thought);
}
/* public instance */ function listen () {
$buffer = $this->buffer;
$this->buffer = "";
while ($r = fgets ($this->pipes[1], 1024)) {
$buffer .= $r;
}
return $buffer;
}
/* public instance */ function getError () {
$buffer = "";
while ($r = fgets ($this->pipes[2], 1024)) {
$buffer .= $r;
}
return $buffer;
}
}

The constructor and member variables should be considered private.

$thread = Thread::create("file.php"); instanciates a thread. $thread->tell("command"); communicates to it, and $thread->listen(); captures its output. $thread->isActive(); exposes whether the code is done running, and $thread->close() releases its references.

To demonstrate it’s usefullnes, I created a simple application to calculate factorials. It uses a common recursive algorithm. It’s notably inefficent but that makes threading easier to understand.

function fact ($n) {
if ($n < 2) return $n;
return fact($n - 1) + fact ($n - 2);
}

Finally, it’s all glued together like this:

include ("Thread.php");
$t2 = Thread::create("t2.php");
$t3 = Thread::create("t3.php");
$t4 = Thread::create("t4.php");
$t5 = Thread::create("t5.php");
while ($t2->isActive() || $t3->isActive() || $t4->isActive() || $t5->isActive()) {
echo $t2->listen();
echo $t3->listen();
echo $t4->listen();
echo $t5->listen();
}
$t2->close();
$t3->close();
$t4->close();
$t5->close();
echo "Main thread done\n";

Each of $t2, $t3, $t4 and $t5 will often represent a discrete task. Therefore, more interaction with the object after listening to it will have more processing. Consider:

$tHVAC = Thread::create("HVAC.php");
$tHVAC->tell("start");
do {
$tHVAC->tell("get current temperature");
$temp = $tHVAC->listen();
if ($temp > 73) {
$tHVAC->tell("start air conditioning");
} else if ($temp < 65) {
$tHVAC->tell(”start heater”);
}
} while (/* application loop */);

This isn’t true multithreading. We’re just running multiple processes, and facilitating communication between them. As each thread is a separate instance or part of your application, its entry point needs to re-include all code you’d normally need: functions and classes established at the time you create a thread are not automatically visable to the new thread. Performance isn’t great, as PHP has to re-parse every file multiple times.

Also, my samples have relied on CLI access to PHP. It is entirely possible to channel your work through your webserver to leverage load balancers or other performance infastructure you have. You’ll have to modify the details of the Thread class, but it’s doable by using curl or wget as a proxy.

Follow up: Communicating with threads in PHP: In response to reader request, learn how to communicate with a PHP thread.

10 Comments currently posted.

Wondering Wombat says:

You didn’t explain how the background object communicates to tell and listen. Please show how HVAC.php communicates with the thread object.

Brian W. Bosh says:

You’re right, I didn’t. It would be by STDIO. I’ll do an article with more detail on that in the next few days.

Ivo Jansch says:

This is cool. The re-parsing disadvantage can be countered by using an accelerator perhaps. But in some situations, this is very useful. Think about fetching content from various sources in parallel, where the overhead of the external resource is much higher than the overhead of this approach.

arif wewe says:

good idea… :)

-mfs says:

Hi Brian,

I want to do following:

querydb to get a set of records

for each record start a new process/thread that will get data from a webservice and log in db.

The idea is that since the webservice can service concurrent requests then I do not want to wait for webservice reponse time before I call it again with the next request.

Can you give me an example of how I would send parameters to the forked process or spawned thread so that the main thread would select records and then give each thread 1 record to process.

Hope this makes sense and you can help. I think this is what Wondering Wombat was asking

Robert says:

the factoral function should be implemented as:
function fact( $n ) {
if ( $n > 2 ) return 1;
return $n * fact(n - 1);
}

… what you have above is the fibonacci numbers

Robert says:

er…
$n = 0

Coder says:

Nice idea!

D says:

You noobs! You are using recursion for factorials!

function factorial($n)
{
$r = 1;
while ( $n > 1 )
{
$n–;
$r = $r * $n;
}
return $r;
}

D says:

$n–;
$r = $r * $n;

–>

$r = $r * $n;
$n–;

Post a comment on this entry: