协程编程注意事项

网友投稿 662 2022-05-29

1.协程内部禁止使用全局变量,以免发生数据错乱;(非多协程协作场景)

原因:协程是共享进程资源的,也就是全局变量共享,用来处理任务时,全局变量很容易被别的协程篡改,导致数据错乱。

2.协程使用 use 关键字引入外部变量到当前作用域禁止使用引用,以免发生数据错乱;

(非多协程协作场景)

原因:引用是原变量的真实地址,由于协程是共享进程资源的,会导致原变量很容易被别的协程篡改,导致数据错乱。

3.不能使用  (非多协程协作场景)

(1)类静态变量 Class::$array

(2)全局变量 $_array

(3)全局对象属性 $object->array

(4)其他超全局变量$GLOBALS   等保存协程上下文内容,以免发生数据错乱;

上下文Context类实际上采用标记协程id的方式来分发存储各个协程对应的数据资源(数据池):

use Swoole\Coroutine;

class Context

{

protected static $pool = []; //进程创建后此静态变量就会存在,但只会根据对应的id去覆盖对应协程下的数据

// 基于协程 `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;

}

// 基于协程 `ID` 写入数据

static function put($key, $item)

{

$cid = Coroutine::getCid();

if ($cid > 0)

{

self::$pool[$cid][$key] = $item;

}

}

// 基于协程 `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.协程之间通讯必须使用通道(Channel)场景:如果需要使用多协程协作执行任务时

Coroutine\Channel 使用本地内存,不同的进程之间内存是隔离的。

只能在同一进程的不同协程内进行 push 和 pop 操作。

不过理论上仍然有共享内存的方式,只是需要进行上锁,保持同步机制

5.不能在多个协程间共用一个客户端连接,以免发生数据错乱;可以使用连接池实现;

原因:同样是因为连接标识共享,有可能前脚一个协程刚对链接做了操作,后脚被别的协程改了数据。(非多协程协作场景)

$pool = new RedisPool();

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

$server->set([

// 如开启异步安全重启, 需要在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) {

//从连接池中获取一个Redis协程客户端

$redis = $pool->get();

//连接失败

if ($redis === false) {

$resp->end("ERROR");

return;

协程编程注意事项

}

$result = $redis->hgetall('key');

$resp->end(var_export($result, true));

//释放客户端,其他协程可复用此对象

$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()

{

//有空闲连接且连接池处于可用状态

if ($this->available && count($this->pool) > 0) {

return $this->pool->pop();

}

//无空闲连接,创建新连接

$redis = new Swoole\Coroutine\Redis();

$res = $redis->connect('127.0.0.1', 6379);

if ($res == false) {

return false;

} else {

return $redis;

}

}

public function destruct()

{

// 连接池销毁, 置不可用状态, 防止新的客户端进入常驻连接池, 导致服务器无法平滑退出

$this->available = false;

while (!$this->pool->isEmpty()) {

$this->pool->pop();

}

}

}

6.在 Swoole\Server 中,客户端连接应当在 onWorkerStart 中创建;

原因:使得客户端链接在整个进程周期中可用。

7.在 Swoole\Process 中,客户端连接应当在 Swoole\Process->start 后,子进程的回调函数中创建;

原因:使得客户端链接在整个子进程周期中可用。

8.必须在协程内捕获异常,不得跨协程捕获异常;

原因:多协程下,try/catch和throw在不同的协程中,协程内无法捕获到此异常。当协程退出时,发现有未捕获的异常,将引起致命错误。

错误:

try {

Swoole\Coroutine::create(function () {

throw new \RuntimeException(__FILE__, __LINE__);

});

}

catch (\Throwable $e) {

echo $e;

}

#try/catch和throw在不同的协程中,

协程内无法捕获到此异常。

当协程退出时,发现有未捕获的异常,将引起致命错误。

正解:

function test() {

throw new \RuntimeException(__FILE__, __LINE__);

}

Swoole\Coroutine::create(function () {

try {

test();

}

catch (\Throwable $e) {

echo $e;

}

});

9.在__get /__set魔术方法中不能有协程切换。(跟php本身有关)

任务调度

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python:设计模式之单例模式
下一篇:随笔003-实战HiLens最佳实践中人脸检测两个小细节
相关文章