Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Swoole Coroutine Co-Program Support


May 14, 2021 Swoole


Table of contents


Swoole starts at 2.0 with the built-in co-program (Coroutine) capability, which provides a co-program capability Swoole\Coroutine\*

PHP7 is supported for 2.0.2 or later

Co-programs can be understood as user-only threads that switch through collaboration rather than preesting. A ll co-program operations can be done in the user state, compared to a process or thread, and it is less expensive to create and switch. Swoole can create a corresponding co-program for each request, which can reasonably schedule the co-program based on the state of the IO, which brings the following advantages:

  1. Developers can achieve asynchronous IO effects and performance without the perception of synchronous code writing, avoiding the discrete code logic brought about by traditional asynchronous callbacks and getting stuck in multiple layers of callbacks that make the code unresponsible.

  2. At the same time, because swoole is encapsulated in the underlying co-program, so compared to the traditional php-layer co-program framework, developers do not need to use yield keywords to identify a co-program IO operation, so there is no need for a deep understanding of yield semantics and every level of the call is modified to yield, which greatly improves development efficiency.

Co-program APIs are currently available in packages for mainstream protocols such as TCP, UDP, including:

  • Udp
  • Tcp
  • HTTP
  • Mysql
  • Redis

To meet the needs of most developers. For private protocols, developers can use co-program TCP or UDP interfaces for convenient encapsulation.

Enable

Prerequisite:

  • PhP version requirements: . . . 5.5, including 5.5, 5.6, 7.0, 7.1
  • Developed swoole_server swoole_http_server onRequet only supported in onReceive onConnect event callback functions.

swoole 2.0 needs to enable co-program capability by adding --enable-coroutine compilation parameters, as follows:

phpize
./configure --with-php-config={path-to-php-config}  --enable-coroutine
make
make install

Add the compilation parameters and the swoole server switches to co-program mode.

When co-program mode is swoole_server swoole_http_server will create a corresponding co-program for each request, and developers can use co-program clients in the onRequet onReceive onConnect 3 event callbacks.

The relevant configuration

Add a configuration parameter max_coro_num to the set method of Swoole\Server max_coro_num configure the maximum number of co-programs processed by a worker process at the same time. Because as the number of co-programs processed by the worker process increases, so does the memory it consumes, to avoid exceeding the memory_limit limit memory_limit set this value based on the actual business pressure measurement results, which defaults to 3000.

Use the example


When the code connect()和recv() swoole triggers a co-switch, at which point the swoole can handle other events or accept new requests. W hen this connection 连接 or the back-end service returns the package, the swoole server resumes the co-program context and the code logic continues to resume execution from the switching point. 回包 T he developer does not need to care about the entire switching process. Use documents that you can refer to.

Precautions

  1. Global variable: Covariate makes the original asynchronous logic synchronous, but the covariate switch occurs implicitly, so the consistency of the global variable and the static variable cannot be guaranteed before and after the covariate switch.
  2. Do not trigger co-switch in the following scenarios:
    • Destructor
    • Magic method __call()
  3. gcc 4.4 If at the time of compilation of swoole (i.e. make stage), gcc warning: dereferencing pointer ‘v.327’ does break strict-aliasing rules dereferencing type-punned pointer will break strict-aliasing rules manually edit Makefile, replace CFLAGS = -Wall -pthread -g -O2 with CFLAGS = -Wall -pthread -g -O2 -fno-strict-aliasing make make clean;make;make install
  4. Not compatible with zend extensions such as xdebug, xhprof, etc., for example, you cannot use xhprof to sample co-program servers for performance analysis.
  5. In PHP5, co call_user_func clients are not available in native call_user_func and call_user_func_array, please use the swoole swoole/Coroutine:::call_user_func and the swoole-coroutine:call_user_func_array instead
  6. Native and primary data can be called directly call_user_func PHP7 call_user_func_array

The list of methods

getDefer()

bool getDefer();
  • Return value: Returns the currently set defer

setDefer()

bool setDefer([bool $is_defer = true]);
  • $is the value of the default, the true, indicates that the Client is delaying the collection, and when it is false, indicates that the Client is not a deferred package, and the default value is true
  • Return value: Set to return true successfully, otherwise return false. Only one case returns false, and when the defer (true) consuper package is set and the recv() package is not received, the defer (false) is set, and then the false is returned.
  • If you need to delay the collection, you need to call it before you issue it

recv()

mixed recv();
  • Return value: Gets the result of a deferred package and returns false when there is no delayed package or the package timeout.

Call at the same time

Client is a request for a number of requests


In the co-program version of, multiple client-side conceding capabilities are implemented.

Typically, if a business request requires a redis request and a mysql request, the network IO will look like this:

redis发包->redis收包->mysql发包->mysql收包

The time of the above process network IO is equal to the redis network IO time and mysql network IO time.

For the co-program version of Client, network IO can be like this:

redis发包->mysql发包->redis收包->mysql收包

The above process network IO time is close to MAX (redis network IO time, mysql network IO time).

Clint, which now supports 2007 requests, has:

  • Swoole\Coroutine\Client
  • Swoole\Coroutine\Redis
  • Swoole\Coroutine\MySQL
  • Swoole\Coroutine\Http\Client

With the exception of Swoole, Coroutine, client, all other Clients implement the defer feature, which is used to declare deferred packages.

Because the sending and receiving methods of Swoole/Coroutine/Client are separate, there is no need to implement the defer feature, while the other Clint's packages and receipts are in one method, so a setDefer() method is required to declare the deferred collection, and then through the recv() method.


Co-program version of Client concentred request sample code:

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

Implementation principle

Swoole 2.0 is setjmp longjmp which automatically saves the memory state of the Zend VM (primarily EG global memory and vm stack) when co-switching.

The sample code

$server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);

#1
$server->on('Request', function($request, $response) {
    $mysql = new Swoole\Coroutine\MySQL();
    #2
    $res = $mysql->connect([
        'host' => '127.0.0.1',
        'user' => 'root',
        'password' => 'root',
        'database' => 'test',
    ]);
    #3
    if ($res == false) {
        $response->end("MySQL connect fail!");
        return;
    }
    $ret = $mysql->query('show tables', 2);
    $response->end("swoole response is ok, result=".var_export($ret, true));
});

$server->start();
  • This program starts only one process and can handle a large number of requests in a synth.
  • The performance of the program is basically the same as that of asynchronous callbacks, but the code is written entirely synchronously

Run the process

  • When onRequest event callback function is coro_create Create a covage (# 1 position) And Saves Both The CPU Register Status and zendvm stack information at this point in time.
  • When mysql->connect is called, coro_save save the state of the current co-program, including the Zend VM context and co-program description coro_yield Give Control of The Program, And The Current Request Is Suspended (# 2 location)
  • After the co-program gives up control of the program, it continues to enter EventLoop to handle other events, while Swoole continues to process Request from other clients
  • After the IO event is complete, the MySQL connection succeeds or fails, the core_resume The Corresponding Co-Program, RESTORES The Zendvm Context, And Continues Down The PHP Code (# 3 Location)
  • mysql->query performs in the same way as mysql->connect and also performs a co-program switching schedule
  • When all operations are complete, end method to return the result and destroy the co-program

Co-program overhead

Co-programs add extra memory footprint than normal asynchronous callback programs.

  • The Swoole 2.0 co-program needs to save the zend stack stack memory for each conse general and maintain the corresponding virtual machine state. If the program is highly syndable, it can consume a lot of memory, depending on the C function, ZendVM call stack depth
  • Co-scheduling adds some additional CPU overhead

Stress test

  • Environment: Ubuntu16.04 + Core I5 4核 + 8G内存 PHP7.0.10
  • Script: ab -c 100 -n 10000 http://127.0.0.1:9501/

Test results:

Server Software:        swoole-http-server
Server Hostname:        127.0.0.1
Server Port:            9501

Document Path:          /
Document Length:        348 bytes

Concurrency Level:      100
Time taken for tests:   0.883 seconds
Complete requests:      10000
Failed requests:        168
   (Connect: 0, Receive: 0, Length: 168, Exceptions: 0)
Total transferred:      4914560 bytes
HTML transferred:       3424728 bytes
Requests per second:    11323.69 [#/sec] (mean)
Time per request:       8.831 [ms] (mean)
Time per request:       0.088 [ms] (mean, across all concurrent requests)
Transfer rate:          5434.67 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       2
Processing:     0    9   9.6      6      96
Waiting:        0    9   9.6      6      96
Total:          0    9   9.6      6      96

Percentage of the requests served within a certain time (ms)
  50%      6
  66%      9
  75%     11
  80%     12
  90%     19
  95%     27
  98%     43
  99%     51
 100%     96 (longest request)