Open Swoole Coroutine

Latest version: pecl install openswoole-22.1.2 | composer require openswoole/core:22.1.5

Version: PHP: 7.1+ and OpenSwoole: 4.4.0+

What is a coroutine?

A coroutine can be simply understood as a thread, but this thread is in user mode and does not require the participation of the operating system. The cost of creating, destroying and switching is very low. Unlike threads, the coroutine cannot use multiple CPU core because it operates in user space but OpenSwoole provides a multi-process model to tackle this limitation.

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing certain code to be suspended and resumed, this action mostly happens when the system is busy waiting for I/O operations to complete, like a database query waiting to return a result, instead of waiting for the result and doing nothing the program is freed up to do something else until the result is available, achieving concurrency.

Open Swoole server creates one coroutine for each request to the server and switches coroutines based on I/O status automatically, this happens within the coroutine schedular.

The advantages of OpenSwoole Coroutines are:

  • Developers can program synchronously, just like traditional PHP but benefit from the performance of asynchronous IO that is provided by coroutines
  • Avoiding callback or multiple level callback hell as coroutines allow for synchronous programming and make code more maintainable
  • Compared with yield/generator coroutines in PHP language, yield is not for I/O switching, OpenSwoole is more convenient because it handles a lot of the I/O switching for you
  • OpenSwoole already provides a range of different coroutine clients which make use of the added performance, for example: HTTP Server, HTTP Client, MySQL Client, Redis Client etc.

Available Methods

Coroutine Example

<?php
co::run(function()
{
    go(function()
    {
        Co::sleep(1);
        echo "Done 1\n";
    });

    go(function()
    {
        Co::sleep(1);
        echo "Done 2\n";
    });
});

The above example shows two coroutines being created using the go function, inside each go call is a coroutine which sleeps for 1 second and outputs text to indicate they have finished, because coroutines switch context based on I/O or when a program is busy, other coroutines can execute while another is waiting or is busy, the example above is using the sleep call to simulate a busy coroutine.

Create or use a coroutine execution context

Coroutines must be executed within a coroutine context, a context is just a wrapped call using co::run and is required because it allows OpenSwoole to run your coroutine under the internal coroutine scheduler.

A coroutine context is created with the callback function: request, receive, connect in a OpenSwoole\Server or OpenSwoole\HTTP\Server for you, so you can start using coroutines straight away within those callbacks.

But ou can also create a coroutine context with co::run yourself if you are not using a OpenSwoole server.

You cannot nest any co::run calls because the coroutine scheduler will have already been started and you cannot run two inside of each other, you must create another context instead.

Sync coroutines with Channel or WaitGroup

Coroutines have access to the same memory space, they can conflict with modifying memory which could be dependant by another coroutine as they are running within user space, on the same thread.

To solve the problem of conflicting memory access we have channels, they are used for the communication between coroutines.

In short, a channel is a non-blocking primitive for sending and receiving array like data between 2 or more coroutines, please visit the channels documentation for more information.

Coroutine clients using a coroutine context

OpenSwoole adds support for a range of different coroutine clients where they create a coroutine context for you.

You can use the following coroutine clients within a coroutine context:

Enable coroutine support for libraries with Hook flags:

Open Swoole provides a runtime hook which allows application code to run synchronously but all underlying I/O operations are run asynchronously. OpenSwoole does this by hooking (or taking control) of native PHP functions and running them using coroutines to achieve concurrent OpenSwoole.

This can have a major benefit on performance because most I/O is slow and by using the runtime hook you can allow other coroutines to operate while waiting for I/O operations to complete.

To learn more take a look at the OpenSwoole Runtime Hook Flags.

Here is a quick list of supported PHP libraries:

  • Redis
  • mysqlnd PDO, mysqli
  • SOAP
  • file_get_contents, fopen, fread, fwrite
  • unlink, mkdir, rmdir
  • stream_socket_client
  • stream_select
  • fsockopen
  • CURL

And many more are supported. To learn more, visit the Runtime Hook Flag documentation.

Other

Important Coroutine Configuration

When using coroutines you can set different configuration options to indicate how OpenSwoole should operate, one of the most important configurations is the maximin number Coroutine that can be created at any given time:

The max number (max_coroutine) of coroutines that can be created by the OpenSwoole server, if this limit is reached new coroutines cannot be created and a 503 response error is returned when using the OpenSwoole HTTP Server. The default value is 3000.

For more configuration options for coroutines, see the OpenSwoole\Coroutine::set method.

Notices & Considerations

  • Global variables: Static variables, classes and $_ global variables may change during coroutine switching because they use the same thread/address space
  • Developer Tools: Xdebug, xhprof, Blackfire can't be used for code profiling currently

Why coroutines are important for concurrency

Web applications or servers contain a lot of different functionality and usually are responsible for performing many different tasks, they handle requests which itself is like a small program until it sends back a response.

During the time one request is handled it would be beneficial if the server could also handle another request at the same time, concurrently. Allowing progress to be made on both requests simultaneously based on when one or another is waiting for I/O operations or busy/idle. This speeds up requests dramatically and allows the developer to still write synchronous code. There is no point in waiting around for I/O to be returned when we can do something else during that time.

Coroutines enable higher throughput because they allow for concurrent connections and don't waste time when a coroutine is busy/idle.

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

The normal process is doing them one after another but with coroutines we can perform each task simultaneously. By using coroutines to get data from a Redis and MySQL server we can reduce the latency because while one is busy waiting for I/O the other can start its request to query the MySQL server, operating concurrently and achieving super efficiently on the server.

Last updated on September 21, 2022