使用类静态变量/全局变量保存上下文
最后更新于:2022-04-02 06:30:27
# 使用类静态变量/全局变量保存上下文
[TOC]
多个协程是并发执行的,因此不能使用类静态变量/全局变量保存协程上下文内容。使用局部变量是安全的,因为局部变量的值会自动保存在协程栈中,其他协程访问不到协程的局部变量。
## 实例
#### 错误的代码
~~~
$_array = [];
$serv->on("Request", function ($req, $resp){
global $_array;
//请求 /a(协程 1 )
if ($request->server['request_uri'] == '/a') {
$_array['name'] = 'a';
co::sleep(1.0);
echo $_array['name'];
$resp->end($_array['name']);
}
//请求 /b(协程 2 )
else {
$_array['name'] = 'b';
$resp->end();
}
});
~~~
发起`2`个并发请求。
~~~
curl http://127.0.0.1:9501/a
curl http://127.0.0.1:9501/b
~~~
* 协程`1`中设置了全局变量`$_array['name']`的值为`a`
* 协程`1`调用`co::sleep`挂起
* 协程`2`执行,将`$_array['name']`的值为`b`,协程2结束
* 这时定时器返回,底层恢复协程1的运行。而协程1的逻辑中有一个上下文的依赖关系。当再次打印`$_array['name']`的值时,程序预期是`a`,但这个值已经被协程`2`所修改,实际结果却是`b`,这样就造成了逻辑错误
* 同理,使用类静态变量`Class::$array`、全局对象属性`$object->array`、其他超全局变量`$GLOBALS`等,进行上下文保存在协程程序中是非常危险的。可能会出现不符合预期的行为。
## 使用 Context 管理上下文
* 可以使用一个`Context`类来管理协程上下文,在`Context`类中,使用`Coroutine::getUid`获取了协程`ID`,然后隔离不同协程之间的全局变量
* 协程退出时清理上下文数据
#### Context
~~~
use Swoole\Coroutine;
class Context
{
protected static $pool = [];
static function get($key)
{
$cid = Coroutine::getuid();
if ($cid < 0)
{
return null;
}
if(isset(self::$pool[$cid][$key])){
return self::$pool[$cid][$key];
}
return null;
}
static function put($key, $item)
{
$cid = Coroutine::getuid();
if ($cid > 0)
{
self::$pool[$cid][$key] = $item;
}
}
static function delete($key = null)
{
$cid = Coroutine::getuid();
if ($cid > 0)
{
if($key){
unset(self::$pool[$cid][$key]);
}else{
unset(self::$pool[$cid]);
}
}
}
}
~~~
#### 正确的代码
~~~
$serv->on("Request", function ($req, $resp) {
if ($request->server['request_uri'] == '/a') {
Context::put('name', 'a');
co::sleep(1.0);
echo Context::get('name');
$resp->end(Context::get('name'));
//退出协程时清理
Context::delete('name');
} else {
Context::put('name', 'b');
$resp->end();
//退出协程时清理
Context::delete();
}
});
~~~
';