协程上下文(context)是每个协程独享、生命周期绑定的键值存储,不可用$_server或static替代;必须启用swoole\runtime::enablecoroutine()并在协程内调用co::getcontext(),跨协程需各自获取而非传递对象。

协程上下文(Context)不是“全局变量的协程版”,而是每个协程独享的、生命周期绑定的键值存储空间;不配置也能用,但漏掉 Swoole\Runtime::enableCoroutine() 或误用全局变量,会导致数据污染和静默故障。
为什么不能直接用 $_SERVER 或 static 存请求 ID
协程共享进程内存,$_SERVER、static $uid、global $trace_id 这类状态在多个协程间会互相覆盖。比如协程 A 设置了 static $user_id = 1001,协程 B 立刻读到的就是 1001 —— 即使它根本没设过。这不是 bug,是 PHP 原生机制在常驻内存模型下的必然结果。
真正安全的做法是:只通过 Co::getContext() 的 set() / get() 操作数据,且不跨协程传递该对象本身。
-
Co::getContext()返回的是当前协程专属的Context实例,每次调用都返回同一个对象(当前协程内) - 协程结束时,其
Context自动销毁,无需手动清理(除非你用Co::defer()做额外资源释放) - 不要把
Context对象存进类属性或静态变量——它只在当前协程内有效
Co::getContext() 的常见误用与正确写法
错误写法示例:
$ctx = Co::getContext();
$ctx->set('user', $user); // ✅ 正确
// ❌ 错误:试图跨协程复用 $ctx 对象
Co::create(function () use ($ctx) {
echo $ctx->get('user'); // 输出 null —— 因为 $ctx 属于父协程,子协程有自己的 Context
});
正确做法是:每个协程内部独立获取上下文:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 在子协程里也调用
Co::getContext(),而不是传入父协程的$ctx - 如需跨函数传递上下文数据,直接传 key 名(如
'trace_id'),而非Context对象 - 若需类实例级隔离(避免多个 Component 对象互相干扰),用
spl_object_id($this)作为 key 前缀,例如:$ctx->set('comp_' . spl_object_id($this), $data)
协程上下文依赖的底层配置必须启用
Co::getContext() 不需要额外配置就能调用,但它依赖协程环境已就绪。如果在非协程上下文中调用(比如 CLI 同步脚本里),会抛出 Fatal error: Uncaught RuntimeException: No coroutine is running。
确保以下两点已生效:
- 服务启动前已调用
Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL)(推荐全量 hook,避免漏掉file_get_contents或curl_exec导致阻塞) - 代码运行在协程内:要么是
go()/Co::create()创建的协程,要么是 Swoole Server 的onRequest、onReceive等回调中(这些回调默认已在协程中执行) - 检查是否误在
onStart、onWorkerStart等非协程回调里调用了Co::getContext()—— 这些阶段尚未进入协程上下文
Context 不解决跨进程/跨机器的数据共享
协程上下文只在当前 worker 进程内有效,不同 worker、不同服务器之间完全隔离。如果你在 HTTP 请求中设置了 trace_id,然后想在另一个 worker 的定时任务里读取它,那是不可能的。
这类场景必须换方案:
- 跨 worker 传参 → 用
Swoole\Table或 Redis - 跨机器追踪 → 用 OpenTelemetry + 分布式 trace ID 注入(如 HTTP header 透传)
- 临时缓存 → 配合
Co::defer()清理,但仅限当前协程生命周期
最容易被忽略的一点:Context 是“自动隔离但不自动传播”的——它不会因为你用了中间件就自动把上一个请求的 trace_id 带到下一个请求。每个新请求必须重新生成并 set,否则 get 出来是 null 或旧值残留(取决于是否触发了协程复用)。










