× 警告!旧版文档已经暂停维护,请查看新版文档。点击前往新版文档

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

多个协程是并发执行的,因此不能使用类静态变量/全局变量保存协程上下文内容。使用局部变量是安全的,因为局部变量的值会自动保存在协程栈中,其他协程访问不到协程的局部变量。

实例

错误的代码

$_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();
    }
});

  • ring

    /a,/b测试了N遍,,未出问题啊。

  • 小继

    @ring 不是说代码出问题,这样回出现逻辑错误

  • jichengyang

    示例代码 使用 curl http://127.0.0.1:9501/a & curl http://127.0.0.1:9501/b 可以测试出 使用类静态变量/全局变量保存协程上下文内容会出现逻辑错误,反之使用 Context 管理上下文 不会出现脏数据