Search code examples
phpasynchronousnonblocking

Does PHP 7 handle requests asynchronously by default?


I have been writing PHP for the last 3.5 years and I love it. When the recent PHP v/s Nodejs buzz came around, the one thing that always got the limelight was the fact that Nodejs implemented non-blocking (or asynchronous) I/O by default and PHP did not.

However, when I recently tried implementing a simple asynchronous request router in PHP (using the exec() function) and tried contrasting my code with a standard (supposedly synchronous request handler script), I noticed something strange.

Before moving further on the explanation, I would like you to take a look at the code below.

sync.php: the one that's supposed to put all further requests on hold until the current one is done processing:

<?php
    $a = $argv[1];
    echo "starting: ".$a;
    $ar = array();
    $sum = 0;
    $ul = 15000;
    for($i = 1; $i < $ul; $i++){
        $ar[$i] = $i;
    }
    for($i = 1; $i < $ul;  $i++){
        for($j = 1; $j < $ul; $j++){
            $sum += $ar[$j];
        }
    }
    echo "\n\nDone: ".$a."\nResult: ".$sum."\n";
?>

I did two kinds of tests.

First, I ran four instances of the terminal on my computer and executed:

php sync.php 1  

in all of them at the same time.

Next, I turned on my XAMPP server, exposed my localhost via serveo and made three requests from three different devices to this script at the same time.

THE RESULTS WERE UNEXPECTED.

No matter how many requests I send, all of them (seem to) get executed in parallel and the result is displayed on all the terminal windows/browser tabs (almost) at the same time (considering the 1-2 second delay incurred while manually running the commands in each terminal window).

This is not in accordance with the fact that PHP implements blocking I/O (that's what occurs to me). What I expected instead is to get a total burst time of

n * 4 seconds

where n is the time taken to process each request (approximately 8 seconds on my laptop).

So, why do the requests get processed asynchronously?? Is it because the for loop is one of the functions that get executed asynchronously or does it not have anything to do with the blocking I/O model at all??


Solution

  • You have misunderstood what is being discussed regarding blocking I/O.

    Each time you run a PHP script from the command-line, it executes in a completely separate process, which doesn't know about any other PHP script running on the machine. You can execute it multiple times for exactly the same reason you can run a web browser and a text editor at the same time - the OS is scheduling the processes on one or more processor cores.

    In the web server example, it's slightly more complex, but the same principle applies: each request you make to the web server creates either a new process, or a new thread within the process, and the PHP script runs inside that.

    What people are discussing regarding blocking I/O is something completely different: when you access something external within your PHP code, such as fetching web content with an HTTP request. Imagine this loop, using an imaginary HTTP library:

    foreach ( $urls as $url ) {
        $results[] =  $httpClient->fetchUrl($url);
    }
    

    If written using the built-in PHP functionality, this will stop running and wait for the remote server to respond each time around the loop. So for 10 requests each taking a second, it will take 10 seconds to complete the loop.

    "Non-blocking I/O" means implementing functions like fetchUrl so that they return immediately, even though the HTTP response hasn't come back. That way, you can run lots of HTTP requests at once: in the example loop, you would get all 10 responses back after 1 second. You'd then have some way to get the results, such as a "promise".

    It's possible to implement this in PHP, but there are no native functions which do it by default and in a user-friendly way.

    More complex still, a system can implement this for input: whenever you're waiting for something external, you can check if a new web request has come in, and start processing that. This can effectively mimic multiple threads, which is how NodeJS is able to handle a large amount of traffic with a thread per request.