协程中直接读写$_SERVER或$GLOBALS必然导致数据错乱,因协程复用进程但不隔离全局符号表;必须改用Swoole\Coroutine::getCid()配合Context类实现协程局部存储,或显式传值、使用Channel通信。

协程里直接读写 $_SERVER 或 $GLOBALS 会出错
协程复用进程,但不隔离全局符号表。一次请求写入的 $_SERVER['request_id'],下一次请求可能还在;$GLOBALS['cache'] 被协程 A 写入后,协程 B 读到的就是脏数据。这不是“偶尔出错”,而是必然发生的数据错乱。
常见错误现象:
- 接口返回上一个用户的 token 或用户 ID
-
$_SERVER['REQUEST_TIME_FLOAT']值不变,导致日志时间戳错乱 - 静态类属性被多个协程轮番覆盖,
User::$current指向错误对象
必须改掉所有直接操作全局变量的习惯,哪怕只是“临时存一下”。
用 Swoole\Coroutine::getCid() + 自定义 Context 类替代全局存储
每个协程有唯一 ID,靠它做键名索引本地数据,是 Swoole 官方推荐的隔离方式。不要自己造“协程上下文管理器”,直接复用已验证的模式:
class Context
{
protected static $pool = [];
public static function get($key)
{
$cid = \Swoole\Coroutine::getCid();
if ($cid <= 0) return null;
return self::$pool[$cid][$key] ?? null;
}
public static function put($key, $value)
{
$cid = \Swoole\Coroutine::getCid();
if ($cid > 0) {
self::$pool[$cid][$key] = $value;
}
}
public static function delete($key = null)
{
$cid = \Swoole\Coroutine::getCid();
if ($cid > 0) {
if ($key) {
unset(self::$pool[$cid][$key]);
} else {
unset(self::$pool[$cid]);
}
}
}
}
使用时:Context::put('user_id', 123),Context::get('user_id') —— 安全、无共享、无副作用。
ThinkPHP 等框架在 Swoole 下的全局污染要额外清理
TP 默认把 Request、Response 塞进 static::$instance 和 $_SERVER,Swoole 复用进程时它们不会自动重置。不处理,第二次请求拿到的是第一次残留的实例。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
必须在每次请求结束前手动清理:
- 调用
$container->setInstances([])清空已解析的单例对象($container->forget()不够) - 跳过核心绑定(如
'app'、'think\App'),只清业务层服务 - 避免在
__get/__set魔术方法中触发协程切换,否则 Context 可能失效
漏掉 setInstances([]) 是内存泄漏最常见原因,不是“忘了清缓存”,是根本没释放对象引用。
协程间传参别用 use 引用,更别碰 &$var
协程共享进程堆内存,use (&$data) 让多个协程指向同一块地址。A 协程刚 push 一个元素,B 协程 pop 出来,结果是 A 的数组少了一项 —— 这种 bug 极难复现,但必现于高并发场景。
正确做法:
- 传值:
use ($data)(复制一份) - 需要通信时走
Swoole\Coroutine\Channel,而不是“我改你读” - 禁止在
go()外部定义大数组/对象再use进去,容易拖慢 GC
真正麻烦的不是语法限制,而是人脑习惯同步编程时,会下意识复用变量。协程不是线程,没有内存保护,写错一行 & 就等于埋雷。

















