Coroutine features are mature in Open Swoole since version 4.x. Swoole provides the powerful CSP (Communicating sequential processes) programming model with three keywords: go
, chan
, defer
.
CSP is an alternative concurrent programming model to the actor model in Erlang
or Akka
. It is well known because it is adopted in Golang
. The key concepts of CSP are:
processes
channels
alternation
Version used in this article: Swoole 4.2.9 PHP 7.2.9
go
, Create a new coroutinechan
, Create a new channel for message-passingdefer
, Delay the task until the exit of the coroutine, FIFOThere is no I/O waste within these three keywords. It is as fast as use Array in PHP. Compare with socket
or file
operations which are blocking the I/O, these three keywords are very fast and cheap.
The keyword go
creates a new coroutine for a function, then the function is running concurrently. If you like to concurrently run a piece of code, just have to put the function into a go
wrapper.
<?php
function test1()
{
sleep(1);
echo "b";
}
function test2()
{
sleep(2);
echo "c";
}
test1();
test2();
Result
time php b1.php
bc
real 0m3.080s
user 0m0.016s
sys 0m0.063s
The function test2
is running after test1
. So the whole script needs three seconds to be finished.
Now let's see the magic of go
, we can put the above test1
and test2
function into go()
.
<?php
Swoole\Runtime::enableCoroutine();
go(function ()
{
sleep(1);
echo "b";
});
go(function ()
{
sleep(2);
echo "c";
});
Swoole\Runtime::enableCoroutine()
switch the PHP build-instream
,sleep
,pdo
,mysqli
,redis
from blocking model to be async model with Swoole Coroutine.
Result
time php co.php
bc
real 0m2.076s
user 0m0.000s
sys 0m0.078s
We can see the whole script only takes 2 seconds which is the time cost of function test2
.
Time cost for sequential processing vs concurrent processing
It is easy to write concurrent programs with go
feature in Open Swoole. The problem we are facing now is how this coroutine collaborates with each other.
Channel
can be created in Open Swoole for exchanging data between different coroutines.
A channel is created by keyword chan
, you are able to choose the size of the channel:
<?php
$chan = new chan(2);
There are two methods can be used to operate the chan
:
<?php
$chan->pop(); // Read data from the channel, it will block and wait if the channel is empty
$chan->push(); // Write data into the channel, it will block and wait if the channel is full
Let's see a real-world use case: concurrently fetch two web pages https://www.google.com and https://www.bing.com. Keep it simple we only fetch the status code of HTTP responses:
<?php
// Create a channel with size 2 and try to read the final result
$chan = new chan(2);
go(function () use ($chan) {
$result = [];
for ($i = 0; $i < 2; $i++)
{
$result += $chan->pop();
}
var_dump($result);
});
// Start fetching the first webpage without blocking the script
go(function () use ($chan) {
$cli = new Swoole\Coroutine\Http\Client('www.google.com', 80);
$cli->set(['timeout' => 10]);
$cli->setHeaders([
'Host' => "www.google.com",
"User-Agent" => 'Chrome/49.0.2587.3',
'Accept' => 'text/html,application/xhtml+xml,application/xml',
'Accept-Encoding' => 'gzip',
]);
$ret = $cli->get('/');
$chan->push(['www.google.com' => $cli->statusCode]);
});
// Start fetching the second webpage without blocking the script
go(function () use ($chan) {
$cli = new Swoole\Coroutine\Http\Client('www.bing.com', 80);
$cli->set(['timeout' => 10]);
$cli->setHeaders([
'Host' => "www.bing.com",
"User-Agent" => 'Chrome/49.0.2587.3',
'Accept' => 'text/html,application/xhtml+xml,application/xml',
'Accept-Encoding' => 'gzip',
]);
$ret = $cli->get('/');
$chan->push(['www.bing.com' => $cli->statusCode]);
});
Result
time php co2.php
array(2) {
["www.google.com"]=>
int(200)
["www.bing.com"]=>
int(200)
}
real 0m0.268s
user 0m0.016s
sys 0m0.109s
We have created three coroutine
with the keyword go
. The first one is trying to get the final result from the other two task
coroutines. We used chan
for the communication between these three coroutines:
Once Coroutine 1 gets the result from the other two, the process will go to the next step and exit the script.
defer
defer
can be used to do some final tasks when coroutines finished the task. Similar to register_shutdown_function
in PHP.
<?php
Swoole\Runtime::enableCoroutine();
go(function () {
echo "a";
defer(function () {
echo "~a";
});
echo "b";
defer(function () {
echo "~b";
});
sleep(1);
echo "c";
});
Result
time php defer.php
abc~b~a
real 0m1.068s
user 0m0.016s
sys 0m0.047s
We can see the sequence of the results: a,b,c then the last deferred task ~b, finally the first deferred task ~a.
More about defer
, please check Coroutine in Open Swoole 4.x vs Coroutine in Golang
Go, Chan and Defer are provided since Open Swoole 4.x, it provides a brand new programming model in PHP: CSP. It can be used for writing TCP Server
, UDP Server
, HTTP Server
with PHP language or PHP CLI
scripts and speed up the tasking related to I/O.
Join 4,000+ others and never miss out on new tips, tutorials, and more.