前面的文章中,我們說過:不能使用類靜態變量 Class::$array
/ 全局變量 global $_array
/ 全局對象屬性 $object->array
/ 其他超全局變量 $GLOBALS
等保存協程上下文內容,以免發生數據錯亂。
那是因為Swoole是常駐內存的,這些全局變量是共享的,在遇到並發請求時,協程A寫入的內容可能會因為協程掛起或協程調度被協程B並發修改了,會導致上下文內容不一致。
解決辦法是加入一個基於協程 ID
來保存上下文的 Context
類,來隔離不同協程之間的上下文/全局變量,然后在協程退出時清理上下文內容。
use Swoole\Coroutine;
class Context
{
protected static $pool = [];
// 基於協程 `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]);
}
}
}
}
使用示例:
$server = new Swoole\Http\Server('127.0.0.1', 9501);
$server->on('request', function ($request, $response) {
if ($request->server['request_uri'] == '/a') {
Context::put('name', 'a');
co::sleep(1.0);
echo Context::get('name');
$response->end(Context::get('name'));
//退出協程時清理
Context::delete('name');
} else {
Context::put('name', 'b');
$response->end();
//退出協程時清理
Context::delete();
}
});
$server->start();
模擬並發測試:
curl http://127.0.0.1:9501/a
curl http://127.0.0.1:9501/b