Swoole Coroutine

Suggested version: PHP: 7.1+ and Swoole: 4.1.0+

Coroutines are computer-program components that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations.

It is purely user mode thread. Compare with thread or process, all the operations in coroutines are happening in user mode, so the cost to create or switch coroutines are cheaper.

Swoole creates one coroutine for each request to the server, switches coroutines based on IO status.

The advantages of Swoole coroutines are:

  • Developers can user sync programming style to archive the performance of async IO. Avoiding callback or multiple level callback in codes.

  • Compare with coroutines in PHP lanaguge, yield is not necessary in the codes for IO switching. It is more convenient.

Most popular clients have already been supported by Swoole Coroutine:

  • TCP/UDP
  • HTTP/HTTP2
  • MySQL
  • Redis

How to enable Coroutine

PHP version: PHP7.0+

Use coroutine clients in the functions: onRequest, onReceive, onConnect inside swoole_server or swoole_http_server.

Configurations

max_coro_num: The max number of coroutine created by the Swoole server. The default value is 3000.

<?php
Swoole\Coroutine::set(array(
    'max_coroutine' => 4096,
));

$http = new swoole_http_server("127.0.0.1", 9501);
$http->on("request", function ($request, $response) {

    // TCP coroutine client
    $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
    // coroutine switch on IO operation connect
    $client->connect("127.0.0.1", 8888, 0.5);
    // coroutine switch back
    $client->send("hello world from swoole");
    // coroutine switch on IO operation recv
    $ret = $client->recv();
    // coroutine switch back
    $response->header("Content-Type", "text/plain");
    $response->end($ret);
    $client->close();
});

$http->start();

Notices

  • Global variables: static global variables may change during coroutine switching.

  • Xdebug, xhprof, blackfire can't be used for code profiling.

Coroutine clients

  • TCP/UDP Client:Swoole\Coroutine\Client
  • HTTP/WebSocket Client:Swoole\Coroutine\HTTP\Client
  • HTTP2 Client:Swoole\Coroutine\HTTP2\Client
  • Redis Client:Swoole\Coroutine\Redis
  • MySQL Client:Swoole\Coroutine\MySQL
  • PostgreSQL Client:Swoole\Coroutine\PostgreSQL

Create coroutine with go method

Table of Contents

Why coroutine is important

Concurrency

Think about the situation when you want to get some data from both Redis server, and MySQL server.

The normal process is: redis send data->redis receive data->mysql send data->mysql receive data.

The time cost of the above process is the IO time of Redis + IO time of MySQL.

With coroutine clients, we can reduce the latency: redis send data/mysql send data->redis receive data/mysql receive data.

The time cost of the process is the max IO time of Redis and MySQL.

setDefer can be used to delay the receive operation.

Example:

<?php
function onRequest($request, $response)
{
    //n concurrent request
    $n = 5;
    for ($i = 0; $i < $n; $i++) {
        $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', 80);
        $cli->setHeaders([
            'Host' => "local.ad.oa.com",
            "User-Agent" => 'Chrome/49.0.2587.3',
            'Accept' => 'text/html,application/xhtml+xml,application/xml',
            'Accept-Encoding' => 'gzip',
        ]);
        $cli->set([ 'timeout' => 2]);
        $cli->setDefer();
        $cli->get('/test.php');
        $clients[] = $cli;
    }

    for ($i = 0; $i < $n; $i++) {
        $r = $clients [$i]->recv();
        $result[] = $clients[$i]->body;
    }
    $response->end(json_encode($data));
}

Example 2:

<?php
$server = new Swoole\Http\Server("127.0.0.1", 9502, SWOOLE_BASE);

$server->set([
    'worker_num' => 1,
]);

$server->on('Request', function ($request, $response) {

    $tcpclient = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
    $tcpclient->connect('127.0.0.1', 95010.5)
    $tcpclient->send("hello world\n");

    $redis = new Swoole\Coroutine\Redis();
    $redis->connect('127.0.0.1', 6379);
    $redis->setDefer();
    $redis->get('key');

    $mysql = new Swoole\Coroutine\MySQL();
    $mysql->connect([
        'host' => '127.0.0.1',
        'user' => 'user',
        'password' => 'pass',
        'database' => 'test',
    ]);
    $mysql->setDefer();
    $mysql->query('select sleep(1)');

    $httpclient = new Swoole\Coroutine\Http\Client('0.0.0.0', 9599);
    $httpclient->setHeaders(['Host' => "api.mp.qq.com"]);
    $httpclient->set([ 'timeout' => 1]);
    $httpclient->setDefer();
    $httpclient->get('/');

    $tcp_res  = $tcpclient->recv();
    $redis_res = $redis->recv();
    $mysql_res = $mysql->recv();
    $http_res  = $httpclient->recv();

    $response->end('Test End');
});
$server->start();

Example 3:

Use channel for concurrency control.

<?php
$serv = new \swoole_http_server("127.0.0.1", 9503, SWOOLE_BASE);

$serv->on('request', function ($req, $resp) {
    $chan = new chan(2);
    go(function () use ($chan) {
        $cli = new Swoole\Coroutine\Http\Client('www.qq.com', 80);
            $cli->set(['timeout' => 10]);
            $cli->setHeaders([
            'Host' => "www.qq.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.qq.com' => $cli->body]);
    });

    go(function () use ($chan) {
        $cli = new Swoole\Coroutine\Http\Client('www.163.com', 80);
        $cli->set(['timeout' => 10]);
        $cli->setHeaders([
            'Host' => "www.163.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.163.com' => $cli->body]);
    });

    $result = [];
    for ($i = 0; $i < 2; $i++)
    {
        $result += $chan->pop();
    }
    $resp->end(json_encode($result));
});
$serv->start();