协程上下文是当前协程私有内存,必须通过co::getcontext()安全访问;全局变量、静态属性或$global会导致数据错乱;跨协程需值传递+显式setcontext;http入口须主动注入trace_id;连接池和日志需动态读取上下文。

协程上下文不是全局变量,别往 $GLOBALS 里塞
协程上下文本质是当前协程私有的内存空间,和线程本地存储(TLS)类似。用 $GLOBALS、静态属性、全局数组存 trace_id 或用户 ID,必然导致数据错乱——因为所有协程共享同一份全局内存。Swoole 提供的 Co::getContext()(或 Swoole\Coroutine::getContext())才是唯一安全入口。
- 每次调用
Co::getContext()返回的是当前协程独有的引用数组,协程退出后自动销毁 - 不要在协程外提前初始化上下文,它只在协程内有效;主协程(如
Co::run外部)调用会返回空数组 - 若需跨协程传递(比如父协程 spawn 子协程),必须显式把上下文内容 copy 进子协程闭包,不能依赖“继承”
协程间传递上下文时,use 变量不能传引用
PHP 协程切换不触发变量引用计数更新,use &$ctx 会导致多个协程指向同一块内存,破坏隔离性。正确做法是值传递 + 显式合并。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 父协程中:$ctx = Co::getContext(); $ctx['trace_id'] = $traceId;
- 子协程中:go(function () use ($ctx) { Co::setContext($ctx); … });
- 如果子协程还需修改上下文(比如生成新 span_id),应在内部调用
Co::getContext()获取副本再改,避免污染父协程上下文 - 切忌在
use中传$_SERVER、$_GET等超全局变量,它们本身就不属于协程上下文
HTTP 请求入口必须主动注入上下文
Swoole HTTP Server 的 onRequest 回调本身不在协程环境里启动,但 Swoole 会自动为每个请求创建协程。此时 Co::getContext() 已可用,但 trace_id 需从请求头提取并写入。
- 检查
$request->header['x-trace-id']是否存在,有则复用;否则生成新trace_id(推荐用bin2hex(random_bytes(8))) - 立即执行
Co::setContext(['trace_id' => $traceId]),确保后续所有 go() 创建的子协程都能读到 - 若使用中间件模式,务必保证上下文注入发生在任何业务逻辑之前,否则下游组件可能读到空 context
- 注意:Swoole 5.0+ 对
Co::setContext()做了优化,但旧版本(如 4.8.x)建议先$old = Co::getContext()再 merge 覆盖,避免覆盖意外字段
连接池和客户端操作必须绑定当前上下文
数据库/Redis 客户端本身无上下文感知能力。如果你在协程里复用连接(比如没配连接池),或在连接上附加 trace_id 标签,就会串 request —— 因为连接被多个协程轮着用,而上下文已变。
- 所有协程化客户端(
Co\Mysql、Co\Redis)必须配合连接池使用,池内每个连接可绑定一个context_id或通过getuid()关联当前协程 - 日志打点时,应从
Co::getContext()动态取trace_id,而不是在连接建立时硬编码进去 - 调用外部 HTTP 服务时,必须把当前
trace_id和span_id注入headers,否则链路断裂;Co\Http\Client不自动透传,得手动加 - defer 清理资源时,如果涉及上下文相关日志(如记录 SQL 执行耗时),也要在 defer 闭包里重新 get context,不能依赖外层变量










