Improved Thread Simulation Class for PHP

Threads. Most PHP scripts do just fine without them. However, when you venture into the domain of complex and numerous database operations, or when you need to deal with datasources of high latency (to put it plainly, HTTP downloads), the multithreading concept starts to look more attractive.

Threading support in PHP is so-so, and the pcntl extension doesn’t work on Windows. Since I’m too lazy to boot up Linux just for the sake of PHP development, I once again went looking for ways to simulate threads (I’ve written about this before).

I created two classes that use asynchronous HTTP POST requests to simulate multithreading (inspired by this class). The Thread simulates a single thread of execution, whereas ThreadManager is an utility class that makes handling multiple threads easier. The script is a bit too long to post here, so …

Download It

thread.zip (3 KB)
Check the examples below for how to use the classes.

Usage Examples

Here you’ll find a few examples of how to use the two classes. You can also take a look inside thread.php for additional info – most class methods contain explanatory comments.

The next code block is common for all examples. In it I include the thread.php file to get access to the classes, and define the test() function which I will use as the example thread function. test($s) simply sleeps for $s seconds and returns a message telling how long it slept.

include_once("includes/thread.php");
function test($s){
	sleep($s);
	return $s." seconds have passed.";
}

Using the Thread class
This example will create two simulated threads and execute them simultaneously. Note that this isn’t the way I’d do it, as handling multiple threads this way is a hassle. However, it shows the basic functions of the class.

$program_start_time = microtime(true);

//Create to thread
$thread1 = new Thread;
/*Set the thread function to execute, and it's arguments. Every
 argument should be passed as an element of the array. The thread
 function receives the arguments normally.*/
$thread1->setFunc('test', array(3));
//Start the thread
$thread1->start();

$thread2 = new Thread;
$thread2->setFunc('test', array(5));
$thread2->start();

//Wait while the threads are running
while (!$thread1->finished || !$thread2->finished){
	$thread1->query();
	$thread2->query();
/* query($seconds, $useconds) waits for a specified time for the thread
and processes it's output (if available), filling the Thread::response and
Thread::result fields. Returns TRUE if the thread is still running,
FALSE otherwise.*/
}

//Output the results
echo "Thread1 : ", $thread1->result,"<br>\n";
echo "Thread2 : ", $thread2->result,"<br>\n";

echo "Total execution time : ".(microtime(true)-$program_start_time)." seconds<br />";

You should get output similar to this :

Thread1 : 3 seconds have passed.
Thread2 : 5 seconds have passed.
Total execution time : 5.01491904259 seconds

Note that you can’t pass resource handles to the thread function. Only variables that can be handled by var_export will work.
Using the ThreadManager class
This is functionally equivalent to the previous example, but more elegant.

$program_start_time = microtime(true);

//Create a ThreadManager instance with default settings
$manager = new ThreadManager;

//Create and start two threads.
$manager->create_thread('test', array(3));
$manager->create_thread('test', array(5));

/* Wait until all threads are finished. ThreadManager::query()
   processes all threads and returns the number of threads that
   are still executing. Check class definition for details.*/
while ($manager->query());

//Output the results
foreach ($manager->finished_threads as $id => $thread){
	echo "Thread '$id' : ", $thread->result,"<br>\n";
}
echo "Total execution time : ".(microtime(true)-$program_start_time)." seconds<br />";

The expected output is something like this :

Thread '_thread_1' : 3 seconds have passed.
Thread '_thread_2' : 5 seconds have passed.
Total execution time : 5.01980900764 seconds

ThreadManager also has a handy pop_finished_thread() function which returns a finished thread and removes it from managers internal lists.

Final Notes

I’m using the class in my DA recommender project and it seems to be working nicely so far. If you have any questions, feel free to comment 😉

Related posts :

20 Responses to “Improved Thread Simulation Class for PHP”

  1. Chris says:

    I have tried the class on two different server environments both give me an error 404!

    Is there anything I need to check?

  2. Chris says:

    @Chris – I take that back- it now gives me a decoding error. Will keep playing.

  3. Chris says:

    @Chris – Got the example code to work. However when running it through my code I get a decoding error 😮

  4. White Shadow says:

    @Chris – It has been quite a while since I wrote/used this class, so I don’t have any ideas off-hand. Anyway, I’d start looking at the “The response was :” part to see why the response can’t be decoded, and work from there.

  5. Balamutikweb says:

    Доброго времени суток! Мне вот тут стало интересно, а есть какие-нибудь он-лайн школы, курсы или что-то подобное, где учат на веб-мастеров? Пытаюсь найти, но ничего не нахожу. Очень хочу стать веб-мастером, слышал, что они хорошо получают. И вообще очень интересно, как это так они работают? И есть ли какие-нибудь спецпредметы, или ещё какие-нибудь специальные дисциплины, которые веб-мастерам знать обязательно? Может кто поможет, если не сложно?
    Слышал, что новичков в этой среде не очень любят – и как тогда быть?

  6. White Shadow says:

    Извините, а вы случайно не спамер? Коммент вроде бы оффтопик, да и на русском… подозрителъно, тощарищ 😛

  7. NikoRoberts says:

    You have to replace “localhost” in the create_thread function to get rid of the decoding error (Line 287)

    Also the “emtpy” should be “empty” on line 40

    Hope this help anyone in the future.

    Other than that it is great code.

    Thanks!

  8. White Shadow says:

    Okay, I fixed some of those bugs.

  9. johnitunes says:

    Yo!
    How are you doing?
    I am new at w-shadow.com and thought I would say HI
    I hope I can become a contributing member!
    Oh yeah… because the economy is so corrupt right now.. I cant afford music.. lol
    Anyone know where I can get free itunes gift cards online?
    Sorry if this is in wrong section admin.. wasnt sure of the right place.

    Thank You!!

  10. […] http://www.phpclasses.org/browse/package/3953.html [14] Improved Thread Simulation Class for PHP http://w-shadow.com/blog/2008/05/24/improved-thread-simulation-class-for-php/ Author: Anton Vedeshin Categories: Articles Tags: Distributed Computing, PHP Comments (0) […]

  11. Bob says:

    I have this working fine on my workstation using XAMPP but when I publish to my production Apache server I get the following

    Decoding error! The response was : HTTP/1.1 404 Not Found Date: Tue, 01 Sep 2009 18:47:57 GMT Server: Apache/2.0.63 (Unix) mod_ssl/2.0.63 OpenSSL/0.9.7a mod_auth_passthrough/2.1 mod_bwlimited/1.4 FrontPage/5.0.2.2635 PHP/5.2.5 Accept-Ranges: bytes Connection: close Transfer-Encoding: chunked Content-Type: text/html 1 1 95
    16 404 Not Found

    43 The server can not find the requested page:

    9 localhost 17 /multithread.php (port 41 80)

    Please forward this error screen to 21 localhost’s WebMaster.

  12. White Shadow says:

    Looks like the script incorrectly determines the current URL. Take a look at the constructor of the Thread class + the Thread->start() method and see if anything looks wrong to you. Also, you might need to explicitly specify the domain name when creating a thread.

  13. Andy says:

    Doesn’t require domain to be specified….requires a minor change in create_thread() and works with any host, local or domain

    function create_thread($func, $arguments=null, $id=null)
    $host = $_SERVER [‘SERVER_NAME’];
    $port = 80;
    …..

    Though it works, I didn’t really understand the mechanism… I mean why doesn’t create_thread() create new threads when the script is sent a Post request and end up in a recursive never-ending loop

  14. Andy says:

    Followup: This has been VERY useful, now I can run parallel search queries in my Store system (4 million products) and reduced search time by a factor of 3.

    But the script had to be patched… specifically the preg_match for [RESPONSE][/RESPONSE] breaks frequently on serialized data. I think it breaks on serialized Arrays because it does work perfectly on String data. Or maybe its because my Arrays contain all sorts of “special” characters e.g. singlequotes, doublequotes and non-ascii characters.

    I copied the encoded string to a test file tested the preg_match on the file. I also tested with different opening/closing tags… and its confirmed that preg_match indeed DOES break. Its a PHP bug, not something todo with this script.

    I have now replaced the preg_match with code which uses strpos() and substr() and it work perfectly now.

  15. White Shadow says:

    If you like, you can post your patch here and I’ll add it to the script.

  16. Rick says:

    Great Function; Thanks So Much For Sharing This! I Ran Into A Problem With Trying To Pass A Multidimensional Array To ‘create_thread’. Using ‘call_user_func’ Instead of ‘var_export’ And ‘eval’ Fixed It.

    ARRAY:
    $user_array = array(array(“first_name” => “Rick”, “last_name” = “Smith”), “id” => 1);

    CALL:
    $manager->create_thread(‘ping’, $user_array);

    ARRAY AFTER BEING PASSED TO FUNCTION USING var_export & eval
    Array
    (
    [0] => Array
    (
    [first_name] => Rick
    [last_name] => Smith
    )
    )

    ARRAY AFTER BEING PASSED TO FUNCTION USING call_user_func
    Array
    (
    [0] => Array
    (
    [first_name] => Rick
    [last_name] => Smith
    )

    [id] => 1
    )

    I Just Made The Following Change To The ‘thread.php’:

    //eval($code);
    $return = call_user_func($_POST[‘f’], $_POST[‘a’]);

  17. sid says:

    Hi,
    thanks a million times for sharing this…
    after I downloaded this almost a year ago (just for being curious) I started to play with it a couple of days ago.
    And it works very well for all sorts of “simple function calls”;

    unfortunately it doesn’t work on more complex scripts.
    My script runs in a single threaded mode for about one or two hours calling mutliple
    subroutines in endless loops and writing results to a binary file.
    Well it does it’s job.

    I’ve modded the code to be -what I thought to be- compatible with your thread manager,
    but unfortunately it isn’t, it has some major flaws I couldn’t fix yet

    Example:
    $testvar = 5;

    function test( $s)
    {
    global $testvar;
    if($testvar<3)
    die("no global variables possible");
    sleep($s);
    return "finished after $s sec.";
    }

    simply dies.
    More complex code only makes it worse….

    I guess I need to rewrite large parts of your code to call files, not functions; maybe this will help for more complex tasks.

    If you have any simple idea though, I'd be happy to hear about it.

    Thanks anyways
    'sid

  18. Jānis Elsts says:

    Hmm, I don’t know why globals wouldn’t work. And if they don’t, why do you think rewriting the code to call files would help?

    Also, if you stop execution with die() in your thread function, you also kill the instance of thread manager that’s running in that thread, so it never gets to pass the output back to the parent script. That might be why it fails.

  19. sid says:

    Hi Jānis,
    first, killing the thread-manager too was intended, otherwise the real code could return false data which would be worse than no data.
    Within these subcalls no new thread mangers should be invoked though.

    I assume, that due to the Post call specifying where to enter the script not all parts of the script are registered correctly.
    Or don’t get registered at all (don’t know yet)

    Most subroutines seem to work at first, most of the time they even include the globals correctly, then at some strange point they stop crashing due to an unreadable global var after some hundred calls where it worked *shrug* very strange.
    And some just don’t either do not get called or do not return properly.

    ‘sid

  20. sid says:

    Sorry for doubleposting, please merge this with my prior post.

    So, I figured that there’s no need to change the thread class, but to create a completely new class containing my code and including that in the called function.
    function calling($cname, $tid)
    {
    include_once(“class_items.php”);
    $rep[$cname] = New RepairItem();
    return $rep[$cname]->calculate($cname, $tid);
    }
    For convenience and since my code takes a very long time to run, I thought I could use my database for a very basic inter thread communication.
    the while loop was changed to:
    while ($manager->query())
    {
    set_time_limit(60);
    if( time() >= $sleeptimer)
    {
    foreach ($manager->active_threads as $id => $thread)
    {
    $q = “SELECT * FROM itc WHERE id = ‘$id’ LIMIT 1”;
    $res = mysql_fetch_assoc(query($q));
    if(!isset($last[$id]) OR $last[$id] !== $res[‘state’])
    {
    echo “Thread ‘$id’ : __”.$res[‘itemid’].”__ needs __”.$res[‘state’].”__ to finish\n”;
    flush();
    $last[$id] = $res[‘state’];
    }
    }
    $sleeptimer = time() + $sleepintvl;
    }
    /*….. more code to reenable finished threads calculating new items …*/
    }
    I’ve set $sleepintvl to 20 so the output is not flooding my window 😉
    My code updates the itc database entries according to the current state; wheras state means how many subitems need to be altered to have a fully functional item…
    Nevermind.

    With my class and 1 of 4 cores @ 100% CPU I get an average of 5 ‘states’ a minute;
    With my class 4 of 4 cores @100% CPU and the threadmanager I get an average of 4 ‘states’ a minute.
    so each thread is around 20% slower than in single threaded mode.
    Well since I can run 4 Threads instead of just one it’s still more than three times faster
    and thus a great improvement, but I’m curios why not gaining the full “four times” ?

    ‘sid

Leave a Reply