OpenSwoole Coroutine Context and Scheduler

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

Coroutines should only be executed within a Coroutine Context, also known as a Coroutine Container.

The function co::run() creates a context which you can then execute coroutines inside of. A Coroutine context is also created for you at the Server callback functions, automatically, so you don't have to do this every time. This is only true if you have Coroutines enabled at the server level, refer to enable_coroutine.

The co::run() function is designed to replace the go() + OpenSwoole\Event::wait() patten as a context for executing coroutines. A Coroutine context is required because it enables them to run inside of whats known as a Coroutine Scheduler, this is what allows Coroutines to automatically switch context for you when I/O is blocking a process, the Coroutine Scheduler can switch to another Coroutine within its context, without co::run() switching becomes a manually task and can be very complex.

There are many ways to have a coroutine context within the ecosystem of OpenSwoole:

  • Within the server (HTTP Server) callback functions/events, a coroutine context is created automatically for you
  • When using the OpenSwoole Process or Process Pool modules
  • Writing code directly within a PHP file, by using co::run() you can use coroutines by themselves

Standalone Example

<?php

use OpenSwoole\Coroutine as Co;

co::run(function()
{
    go(function()
    {
        Co::sleep(1);
        echo "Coroutine 1 is done.\n";
    });

    go(function()
    {
        Co::sleep(1);
        echo "Coroutine 2 is done.\n";
    });
});

echo "Outside any Coroutine Context.\n";

To test the code above for yourself, simply place the code in a file like coroutines.php and run from the command line php coroutine.php. The function co::run() creates the coroutine context and can be thought of like Java or C main function.

The co::run() function is part of the OpenSwoole\Coroutine namespace but to make development easier it is also provided as a function using the namespace: use function OpenSwoole\co::run.

The go() function is also part of the OpenSwoole\Coroutine namespace but is defined as a function to make development easier. So you can reference use function OpenSwoole\Coroutine\go as well.

Keep in mind that the run in co::run is lowercase


Concurrent Execution

You can concurrently execute two things at once with OpenSwoole Coroutines:

<?php

use OpenSwoole\Coroutine as Co;

co::run(function()
{
    go(function()
    {
        // Runs immediately
        var_dump(file_get_contents("http://openswoole.com/"));
    });

    go(function()
    {
        // Waits once second before continuing
        Co::sleep(1);
        var_dump(file_get_contents("http://www.google.com/"));
    });
});

echo "Outside any Coroutine Context.\n";

Coroutine Context Nesting

It is not possible to nest co::run because you can only create one context per set of Coroutines but you may create multiple, separate co::run context containers on their own.

If you try to nest co::run you will get an error something similar to:

PHP Warning:  OpenSwoole\Coroutine\Scheduler::start(): eventLoop has already been created. unable to start OpenSwoole\Coroutine\Scheduler in @swoole-src/library/core/Coroutine/functions.php on line 24

The event-loop has already been started in the initial co::run so starting another will cause conflict and is not allowed. Instead you need to create multiple co::run containers separately like so:

<?php

// Context 1
co::run(function()
{
    ...
});

// Context 2
co::run(function()
{
    ...
});

...

Note: Even though the above code will work, co::run will block until all the events within the context have executed, the context must finish first before code execution continues. To solve this limitation, we can use the Coroutine Scheduler.


Coroutine Scheduler

The function co::run is actually just a wrapper for the OpenSwoole\Coroutine\Scheduler class. Still part of the main OpenSwoole\Coroutine namespace but co::run is just an easy and quick way to create one coroutine context which executes at a time.

For more advanced usage, you can use the Coroutine Scheduler which allows you creates multiple coroutine context containers which all run at once and do not block each other.

Scheduler Example

<?php

// Multiple Coroutine Context Containers at once

$run = new OpenSwoole\Coroutine\Scheduler;

// Context 1
$run->add(function()
{
    Co::sleep(1);
    echo "Context 1 is done.\n";
});

// Context 2
$run->add(function()
{
    Co::sleep(1);
    echo "Context 2 is done.\n";
});

// Context 3
$run->add(function()
{
    echo "Context 3 is done.\n";
});

// Required or context containers won't run
$run->start();

// Or one context at a time

co::run(function()
{
    Co::sleep(1);
    echo "co::run context is done.\n";
});

With the Coroutine Scheduler example you will see the output of:

Context 3 is done.
Context 1 is done.
Context 2 is done.

This is because we have used the Coroutine Scheduler to start multiple context containers which run under a single event-loop and are able to run simultaneously. Whereas out single co::run` example can only run one after another.

The Coroutine Scheduler is actually a low-level API, giving you access to OpenSwoole at a low level, this should only really be used for advanced use cases.


Coroutine Scheduler API

The Coroutine Scheduler is a low-level API to OpenSwoole, most of the time you should not need to use the Coroutine Scheduler yourself, it is managed for you but the API is provided to you.

set()

<?php
OpenSwoole\Coroutine\Scheduler->set(array $options): bool

Set the runtime parameters of the coroutine scheduler, related to Coroutine options only. Refer to the specific Coroutine options for details.

This method is actually just an alias of OpenSwoole\Coroutine::set().

Example

<?php
$scheduler = new OpenSwoole\Coroutine\Scheduler;
$scheduler->set(['max_coroutine' => 100]);

getOptions()

<?php
OpenSwoole\Coroutine\Scheduler->getOptions(): null|array

Get the set runtime parameters of the coroutine scheduler, these settings are set using set().

This method is an alias of OpenSwoole\Coroutine::getOptions(). Refer to the coroutine documentation.


add()

<?php
OpenSwoole\Coroutine\Scheduler->add(callable $fn, ... $args): bool

Add new Coroutine Context to the Scheduler. These can be thought of as tasks to run, they only get executed once you call $Scheduler->start(). This is a main method when using the Scheduler as it allows you to add multiple context containers at a time.

  • $fn

    The callback function to execute when the Coroutine Context starts

  • $args

    You may use this parameter to pass optional arguments to the specified function

Example

<?php

use OpenSwoole\Coroutine;

$scheduler = new Coroutine\Scheduler;

// Context 1
$scheduler->add(function($a, $b)
{
    Coroutine::sleep(1);

    echo assert($a == 'Hello ') . PHP_EOL;
    echo assert($b == 'World!') . PHP_EOL;


    echo "Context with assert has completed.\n";
}, 'Hello ', 'World!');

// Context 2
$scheduler->add(function()
{
    echo "I will finish first.\n";
});

// Blocks until all tasks complete
$scheduler->start();

The above code will show:

I will finish first.
1
1
Context with assert has completed.

Once you call $scheduler->start() the Scheduler will start each context and execute them at the same time, the first context waits one second, allowing you to see that context 2 runs immediately and is not blocked by the context 1 sleep call.


parallel()

<?php
OpenSwoole\Coroutine\Scheduler->parallel(int $num, callable $fn, ... $args): bool

Add new Coroutine Context to the Schedule but execute a number of parallel tasks using Coroutines. This method will start Coroutines simultaneously when the Scheduler begins and execute Coroutines in parallel.

  • $num

    The number of coroutines to start in parallel.

  • $fn

    The callable function to use when starting parallel tasks/coroutines.

  • $args

    Optional function arguments to pass to each new coroutine that is started with the $fn callable.

Example

<?php

use OpenSwoole\Coroutine;

$scheduler = new Coroutine\Scheduler;

$scheduler->parallel(10, function($time, $msg)
{
    Coroutine::sleep($time);

    echo $msg . " from co " . Coroutine::getCid() . "\n";

}, 0.05, 'Hello World!');

// Blocks until all tasks complete
$scheduler->start();

Order of execution when started is not guaranteed


start()

<?php
OpenSwoole\Coroutine\Scheduler->start(): bool

Start the Coroutine Scheduler, begin any context containers which were created from add() or parallel(). If you do not call this method, no context containers or Coroutines will execute.

This method will return true if there were no errors and a successful start in adding each task, in the case of any problems, it will return false in regard to adding any new tasks. You may get errors if a Scheduler has already been started/created and cannot begin a new one.

Last updated on February 9, 2023