When I started working with HHVM in summer 2013 async and await were one of first keywords that grabbed my attention. I realised that on Facebook scale it might be necessary to send database queries or HTTP requests asynchronously in order to send a response back to a user in a timely manner. Back in those days PHP seemed to be missing any similar feature. Couple of months later I read a blog post by Nikita Popov that demonstrated how to achieve asynchronism while running on Zend Engine. A yea and a half later I decided to learn if it would be feasible to use Popov’s method in some real-life applications.
First of all we should probably familiarise ourselves with cooperative multitasking. In cooperatively multitasking environment it is up to a process (or a task) to stop operating and yield CPU time to other processes (or tasks). Operating system never initiates a context switch. As this approach might be tempting one should note its significant weakness – if a process fails to stop operating voluntarily then a dead lock might occur and no other processes will be ever allowed to run. This approach is rarely used nowadays but remains the only feasible one at web scale – I can hardly imagine myself an AWS instance that could handle 100 requests per second if each of them spawned 5 or 6 threads.
Assuming that one would like to desynchronise an application without abandoning PHP (that is quite a popular programming language for web applications) one would have to dive deep into generators. If you, dear reader, are not familiar with PHP then you might be used to C#, C++, Perl or EcmaScript 6 that utilise very similar syntax (idea of generators is available in many more languages, including Java). If you are a PHP developer that is a stranger to this relatively new idea I would recommend to take a quick look at the manual. I think that shortest possible explanation of generators in PHP is: Generator is a class that implements Iterator interface and can be instantiated in rather an unusual way. The code snippet below is an example of generator and its usage:
$iterateOverMe = function()
{
yield 1;
yield 2;
yield 3;
};
foreach ($iterateOverMe() as $value) {
var_dump($value);
}
It is possible to achieve cooperative multitasking using yield keyword. An example below (from Popov’s blog post) is quite a good illustration:
$socket = stream_socket_server("tcp://localhost:10666");
stream_set_blocking($socket, 0);
$socket = new CoSocket($socket);
while (true) {
yield newTask(
handleClient(yield $socket->accept())
);
}
As you may now realise – yield
is a statement responsible for interrupting linear flow of program. We should probably focus on this very keyword for a while so its three mutations and a reverse will become clear. First of all a value can be yielded with following statement:
yield $someValue
It will interrupt execution and allow it to continue at the location where value was being retrieved from generator (using Generator::send()
or Generator::current()
methods).
Yet another usage of yield
, introduced in PHP7, allows us to pass execution into another generator, a sub generator; this feature allows us to build generators from smaller blocks of code while hiding implementation details from the outer world. Keep in mind that in a following snippet someFunction()
needs to return an instance of Generator
:
yield from someFunction
Third yield use-case is closely related with Generator::send() method that allows us to communicate from the code using generator to the generator itself. Any variable that will be passed as an argument to send() method will become available inside a Generator instance thanks to following syntax:
$aVariableFromTheOuterScope = yield;
Let’s consider following example of simple coroutine implementation:
/**
* Generator
* @return integer
*/
$generatorWithTwoWayCommunication = function()
{
$randomValue = mt_rand();
yield $randomValue;
$receivedFromOuterSpace = yield;
yield $receivedFromOuterSpace * $receivedFromOuterSpace;
return $receivedFromOuterSpace * $randomValue;
};
/**
* Main flow
*/
$generator = $generatorWithTwoWayCommunication();
$valueFromFirstIteration = $generator->current();
$generator->next();
$valueFromSecondIteration = $generator->send(666);
$generator->next();
$valueReturnedFromGenerator = $generator->getReturn();
The image below explains the execution flow of generator return expression example.
As you can see it is nothing more then a clever usage of Iterator interface that allows to create coroutines in PHP. The example above utilises new PHP 7 feature – generator return expressions. None of PHP 5 versions allows to return a value from generator.
In the next part of the series I will introduce you to the concept of generator delegation that allows to aggregate many generators into a single one. I will also provide my thoughts on asynchrony in PHP and recommend the path that I would follow myself.
Leave a comment