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:

Most popular clients have already been supported by Swoole Coroutine:

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

Coroutine clients

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();