协程本地变量必须用Co::getContext()或Swoole\Coroutine\Local,禁用$GLOBALS/static;因Swoole常驻内存,协程共享进程空间,static变量会互相污染导致登录态错乱、trace_id混用等必然并发错误。

协程本地变量必须用 Co::getContext() 或 Swoole\Coroutine\Local,直接写全局变量或 static 属性一定会出错。
为什么不能用 $GLOBALS 或 static 变量
因为 Swoole 是常驻内存的,所有协程共享同一块进程内存。一个协程改了 static $user_id = 123,另一个协程紧接着读,可能拿到的是上一个请求留下的值,或者被中间某个协程覆盖掉 —— 这不是“偶尔出错”,而是并发下必然发生。
常见错误现象包括:
- 日志里看到用户 A 的操作打出了用户 B 的
trace_id - 接口返回了上一次请求缓存的响应体
- 登录态校验失败,明明刚登录却提示未授权
这些都不是代码逻辑问题,而是变量作用域没隔离清楚。
Co::getContext() 的正确用法
这是最轻量、最推荐的协程上下文方式,适用于存储字符串、整数、数组等简单结构。
关键点:
- 不传参数调用
Co::getContext()默认返回当前协程的上下文对象 - 它本质是个可读写的
ArrayObject,支持->set()/->get(),也支持数组式赋值:$ctx['uid'] = 1001 - 协程退出时上下文自动销毁,无需手动清理(除非你用了
Co::defer()做资源释放)
示例:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
go(function () {
$ctx = Co::getContext();
$ctx->set('uid', 1001);
$ctx['trace_id'] = 'req-abc';
Co::sleep(0.1);
echo $ctx->get('uid'); // 1001
echo $ctx['trace_id']; // req-abc
});
需要对象实例时用 Swoole\Coroutine\Local
当你要存的是一个类实例(比如 DbConnection、Logger),且需要在协程内多次复用同一个对象时,Co::getContext() 不够直观,这时用 Swoole\Coroutine\Local 更安全。
它会为每个协程创建独立副本,避免对象方法内部状态被其他协程污染。
使用注意:
- 必须在协程内首次访问时初始化,不能在协程外 new 出来再传入
- 不能跨协程传递该对象实例(哪怕你把它塞进
Co::getContext()) - 它不提供自动清理机制,如果对象持有资源(如 socket、file handle),需配合
Co::defer()显式释放
示例:
$local = new \Swoole\Coroutine\Local();
go(function () use ($local) {
$local->db = new \PDO('sqlite::memory:');
Co::defer(function () use ($local) {
$local->db = null; // 主动释放
});
});
别踩 Co::getContext($cid) 的坑
传入协程 ID 获取其他协程的上下文,看似能“跨协程通信”,但实际非常危险:
- 父协程还没写完,子协程就去读
Co::getContext($pcid)→ 拿到null或旧值 - 协程已退出,
$cid对应的上下文已被回收,再访问会报Segmentation fault或静默失败 - 无法保证执行顺序,调试困难,极易变成偶发性故障
真正需要跨协程传数据,应该用 Swoole\Coroutine\Channel 或函数参数显式传递,而不是靠“偷看别人上下文”。
复杂点在于:Context 不是万能胶水,它只解决“本协程内数据隔离”,不解决“协程间协作”。混淆这两者,是线上事故最常见的源头之一。

















