Precautions for collaborative programming

Posted by lansing on Thu, 17 Feb 2022 04:57:14 +0100

1. It is forbidden to use global variables in the collaborative process to avoid data disorder; (non multi process collaboration scenario)

Reason: the collaboration process shares process resources, that is, the global variables are shared. When used to process tasks, the global variables are easy to be tampered with by other collaboration processes, resulting in data disorder.

2. The use keyword is used in the collaboration process to introduce external variables into the current scope. References are prohibited to avoid data confusion;

(non multi process collaboration scenario)

Reason: the reference is the real address of the original variable. Since the coroutine shares process resources, the original variable can be easily tampered with by other coroutines, resulting in data disorder.

3. Cannot be used (non multi process collaboration scenario)

(1) Class static variable Class::$array

(2) Global variable$_ array

(3) Global object attribute $object - > array

(4) Other super global variables such as $GLOBALS save the context content of the collaboration process to avoid data confusion;

The Context class actually distributes and stores the data resources (data pool) corresponding to each process by marking the process id:

use Swoole\Coroutine;
class Context
{
   protected static $pool = []; //After the process is created, this static variable will exist, but it will only overwrite the data under the corresponding collaboration according to the corresponding id
 
    
// Get data based on collaboration 'ID'
    static function get($key)
{
        $cid = Coroutine::getCid();
        if ($cid < 0)
        {
            return null;
        }
        if(isset(self::$pool[$cid][$key])){
            return self::$pool[$cid][$key];
        }
        return null;
    }
 
    // Write data based on the collaboration 'ID'
    static function put($key, $item)
{
        $cid = Coroutine::getCid();
        if ($cid > 0)
        {
            self::$pool[$cid][$key] = $item;
        }
 
    }
 
    // Delete data based on collaboration 'ID'
    static function delete($key = null)
{
        $cid = Coroutine::getCid();
        if ($cid > 0)
        {
            if($key){
                unset(self::$pool[$cid][$key]);
            }else{
                unset(self::$pool[$cid]);
            }
        }
    }
}

4. The communication between collaborative processes must use the Channel scenario: if it is necessary to use multiple collaborative processes to perform tasks

Coroutine\Channel uses local memory, which is isolated between different processes.

push and pop operations can only be performed in different processes of the same process.

However, in theory, there is still a way to share memory. It just needs to be locked and keep the synchronization mechanism

5. One client connection cannot be shared among multiple processes to avoid data disorder; It can be realized by using connection pool;

Reason: it is also because the connection ID is shared. It is possible that one of the front processes has just operated on the link, and the data of the back process has been changed by another process. (non multi process collaboration scenario)

$pool = new RedisPool();
$server = new Swoole\Http\Server('127.0.0.1', 9501);
$server->set([
    // If asynchronous safe restart is enabled, the connection pool resources need to be released in workerExit
    'reload_async' => true
]);
$server->on('start', function (swoole_http_server $server) {
    var_dump($server->master_pid);
});
$server->on('workerExit', function (swoole_http_server $server) use ($pool) {
    $pool->destruct();
});
$server->on('request', function (swoole_http_request $req, swoole_http_response $resp) use ($pool) {
    //Obtain a Redis collaboration client from the connection pool
    $redis = $pool->get();
    //connection failed
    if ($redis === false) {
        $resp->end("ERROR");
        return;
    }
    $result = $redis->hgetall('key');
    $resp->end(var_export($result, true));
    //Release the client, and other processes can reuse this object
    $pool->put($redis);
});
$server->start();

class RedisPool
{
    protected $available = true;
    protected $pool;

    public function __construct()
{
        $this->pool = new SplQueue;
    }

    public function put($redis)
{
        $this->pool->push($redis);
    }

    /**
     * @return bool|mixed|\Swoole\Coroutine\Redis
     */
    public function get()
{
        //There are free connections and the connection pool is available
        if ($this->available && count($this->pool) > 0) {
            return $this->pool->pop();
        }

        //No idle connection, create new connection
        $redis = new Swoole\Coroutine\Redis();
        $res = $redis->connect('127.0.0.1', 6379);
        if ($res == false) {
            return false;
        } else {
            return $redis;
        }
    }

    public function destruct()
{
        // The connection pool is destroyed and placed in an unavailable state to prevent new clients from entering the resident connection pool, resulting in the server being unable to exit smoothly
        $this->available = false;
        while (!$this->pool->isEmpty()) {
            $this->pool->pop();
        }
    }
}

6. In Swoole\Server, the client connection should be created in onWorkerStart;

Reason: make client links available throughout the process cycle.

7. In the Swoole\Process, the client connection should be created in the callback function of the child process after the Swoole\Process - > start;

Reason: make the client link available throughout the sub process cycle.

8. Exceptions must be captured within the collaboration process and not across the collaboration process;

Cause: in the case of multiple cooperation processes, try/catch and throw are in different cooperation processes, and this exception cannot be caught in the cooperation process. When the collaboration exits, an uncapped exception is found, which will cause a fatal error.

Error:
try {
    Swoole\Coroutine::create(function () {
        throw new \RuntimeException(__FILE__, __LINE__);
    });
}
catch (\Throwable $e) {
    echo $e;
}
#try/catch and throw are in different processes,
This exception cannot be caught within the coroutine.
When the collaboration exits, an uncapped exception is found, which will cause a fatal error.

Positive solution:
function test() {
    throw new \RuntimeException(__FILE__, __LINE__);
}
Swoole\Coroutine::create(function () {
    try {
        test();
    }
    catch (\Throwable $e) {
        echo $e;
    }
});

9. In__ get /__set magic method cannot have co process switching. (related to php itself)