swoole协程中全局变量会导致跨请求数据污染,因worker进程常驻内存且协程共享全局符号表;应禁用global、static状态和超全局变量模拟上下文,改用coroutine\local或co::getcontext()实现协程隔离。

在Swoole协程中直接使用全局变量会导致不同请求间数据串扰、用户信息错乱、计数器异常累加甚至内存泄漏,因为Worker进程常驻内存,所有协程共享同一块全局符号表。
为什么全局变量在协程里会出问题
PHP-FPM下每次请求都新建进程,$_GET、static $counter、global $config等随请求结束自动销毁;Swoole的Worker进程却一直运行,这些变量持续存在并被后续所有协程复用——A用户登录后写入$user_id = 1001,B用户下次请求进来直接读到1001,而不是空或自己的ID。
【关键前提】只要没显式清理或隔离,任何在onWorkerStart之后定义的全局变量(包括类静态属性、$GLOBALS、未封装的顶层变量)都会跨请求污染。
立刻停用的三类危险写法
方法一:删掉所有global声明
把global $cache; $cache['order'] = $data;这种代码全部替换为参数传递或Context存储。
方法二:禁止类中用static存状态
像class User { private static $id; public static function setId($v) { self::$id = $v; } }这类单例或状态缓存,在协程里调用两次就互相覆盖。
方法三:不用超全局变量模拟上下文
不要用$_SERVER['user_id'] = $uid或$_SESSION = ['token' => $t]来传用户数据——Swoole HTTP Server中$_SERVER默认不更新,且所有协程共用同一份内存地址。
安全替代方案:Coroutine\Local 实现协程级隔离
第一步:声明一个协程本地容器
在Server启动前初始化:$local = new \Swoole\Coroutine\Local();
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
第二步:在onRequest中绑定当前请求数据
use ($local) { go(function () use ($local, $request) { $ctx = new stdClass(); $ctx->userId = $request->get['uid'] ?? 0; $ctx->traceId = uniqid('req_'); $local->data = $ctx; }); }
第三步:业务逻辑中直接读取
function getOrderList() { $ctx = \Swoole\Coroutine\Local::getInstance()->data; return $ctx->userId > 0 ? fetchByUserId($ctx->userId) : []; }
这一步操作起来很简单,每个协程拿到的$local->data都是独立副本,改写不会影响其他协程。注意:不能在协程外访问$local->data,否则返回null。
更推荐的轻量方案:Swoole\Coroutine::getContext()
方法一:HTTP请求开头统一注入
onRequest回调第一行写:Co::getContext()['user'] = $parsedUser; Co::getContext()['trace_id'] = $request->header['x-trace-id'] ?? '';
方法二:中间件或服务层直接取值
$user = Co::getContext()['user'] ?? null; if (!$user) { throw new Exception('Unauthorized'); }
方法三:协程退出前手动清理(可选)
在响应发送完毕后执行unset(Co::getContext()['user']);避免Context数组无限膨胀——虽然Swoole 6.1.1已优化自动回收,但高频写入场景仍建议显式释放。
【不可逆操作】不要在getContext()里存资源句柄(如PDO实例),必须走连接池;存大对象(如原始JSON字符串)要评估内存占用,协程退出后Context才释放。










