使用类静态变量/全局变量保存上下文

最后更新于: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(); } }); ~~~
';