
协程里直接用普通变量会出问题吗
会,而且大概率不是“偶尔出错”,是“必然错”。PHP协程(如 Swoole 4.4+ 或 Hyperf 的协程)本质是单线程内多任务调度,$_SERVER、$_GET、全局变量、静态属性这些在协程间是共享的。一个请求改了 $GLOBALS['user_id'],另一个并发请求可能立刻读到错误值。
- 典型现象:
Undefined index: user_id或用户 A 登录后看到用户 B 的订单数据 - 根本原因:协程切换不触发变量隔离,PHP 默认没有协程上下文自动绑定
- 不是“不能用变量”,而是“不能用非协程安全的方式存取”
怎么安全地在协程中存取用户相关变量
必须用协程上下文(Coroutine Context)机制,Swoole 提供 Co::getContext() 和 Co::setContext(),Hyperf 封装为 ApplicationContext::getContainer()->get(CoroutineContext::class)。别碰 global、static、$_SESSION(没启动 session 协程适配时)。
- 推荐方式:用
Co::getContext()获取当前协程私有数组,写入键值对,例如Co::getContext()['user_id'] = 123 - 避免方式:不要在协程函数里初始化
static $cache = [],它会在所有协程间复用 - 注意兼容性:PHP 8.1+ 的
fiber原生协程不内置上下文 API,需自行配合Fiber::getThis()+ 弱引用映射
类属性和依赖注入容器在协程里安全吗
不绝对安全,取决于生命周期管理。Hyperf 默认的 @Inject 类是单例,属性一旦被某个协程修改,其他协程立刻可见;Laravel Octane 的 Request 对象虽是每次请求新建,但若你在中间件里给 $request->attributes 赋了引用类型值,仍可能泄漏。
- 安全做法:把需要协程隔离的数据显式注入进服务类构造函数,或通过方法参数传入,而非依赖类属性缓存
- 危险操作:在
__construct()中读取$_SERVER并赋给$this->clientIp—— 下个协程复用该实例时 IP 就错了 - 性能提示:频繁调用
Co::getContext()几乎无开销,比用uniqid()手动打标再查 map 快得多
调试时怎么确认变量到底属于哪个协程
靠日志打点最直接。别信 IDE 的“当前变量值”,协程切换后它早不是你认为的那个上下文了。加一句 echo 'cid='.Co::getCid().', user_id='.($ctx['user_id'] ?? 'missing').PHP_EOL; 能立刻暴露混用。
立即学习“PHP免费学习笔记(深入)”;
- 常见坑:在
go(function () { ... });外部定义变量,然后在闭包里直接用 —— 这个变量是闭包创建时捕获的,不是运行时协程上下文里的 - 验证手段:在异步 MySQL 查询前后各打一次
Co::getCid(),如果不同,说明中间发生了协程让出,此时所有非上下文变量都不可信 - 工具建议:Hyperf 自带
co::stats()可查当前活跃协程数,结合debug_backtrace()定位变量污染源头
协程变量安全的核心就一条:凡涉及请求生命周期的数据,必须绑定到 Co::getContext() 或等效上下文对象上。忘了这句,后面所有优化、缓存、中间件逻辑都可能变成定时炸弹。










