Coroutine in PHP Swoole 4.x vs Coroutine in Golang

The design of coroutine in PHP Swoole is similar to the coroutine in Golang:

  • Each coroutine has it's own stack.
  • The context switching between different coroutine is controlled by scheduler.

But there are some differences between the coroutine in PHP Swoole and the coroutine in Golang:

Threads design in the coroutine scheduler

The coroutine scheduler design in Golang

The coroutine scheduler in Golang is multiple threads design. There are multiple coroutines parallel running, it is necessary to solve race conditions for the global shared resources, to do synchronization and lock the critical section, please see more at: https://golang.org/pkg/sync/.

Although Golang has solved the problem of synchronizing between units of work by communicating over shared channels. But Golang still used mutex and locks underlayer.

The coroutine scheduler design in PHP Swoole 4.x

The coroutine scheduler in PHP Swoole 4.x is signle thread design. Only one coroutine is parallel running, so there is no problems such as data syncing between threads and avoid locking.

But how to share global variables and resources between different Swoole processes? There are three ways to solve this problem:

  • Use Swoole Table or Swoole Atomic or share memory structure
  • Use IPC, communicate between processes
  • Use external data storage such as Redis, MySQL, or file operation

Defer in Golang vs Defer in Swoole

defer in Golang

defer in Golang is binding with a function. The deferred task will be executed when the function is finished. This is used to clean up and release the resources created and used within the function.

For example, in Golang:

func test() {
    db := new(database)
    close := db.connect()
    defer close()

    fmt.Println("query db...")
}

defer task is needed to release the connection resources. Compare with the PHP style doing the defer task within destructor function.

defer is doing similar tasks in Golang as destructor in PHP.

defer in Swoole 4.x

Looking at this piece of normal PHP code:

<?php
function test() {
    $db = new Database;
    $db->connect('127.0.0.1', 6379);
    $db->query($sql);
}

Compare with the Golang test function, it is not necessary to close the db connection in PHP since PHP destroy and clean up the db object created within the function. Because of the destructor funciton including logic of closing the connection in Database will be called before the db object is destroy.

defer in Swoole 4.x is designed to be executed when the coroutine task is finished. It is binding with coroutine task.

Shared socket resource

In Golang, it is possible to read the same socket with multiple coroutine concurrently. But the developer need to control when to do so. For example, the following Golang code may throw errors:

func test() {
    db := new(database)
    close := db.connect()

    go func(db) {
        db.query(sql);
    } (db);

    go func(db) {
        db.query(sql);
    } (db);
}

Compare with Golang, reading socket concurrently with multiple coroutine is prohibited.

<?php
function test() {
    $db = new Database;
    $db->connect('127.0.0.1', 6379);

    go(function () use ($db) {
        $db->query($sql);
    });

    go(function () use ($db) {
        $db->query($sql);
    });
}

The above code throws Error:

"%s has already been bound to another coroutine#%ld, reading or writing of the same socket in multiple coroutines at the same time is not allowed."

If you like to reading socket concurrently, use Swoole Channel or SplQueue to build a connection pool to control the access to the connection resource.