workerman中global变量因进程常驻导致状态污染和并发不安全,应改用onworkerstart初始化进程级依赖或外部存储共享状态。

Workerman进程常驻导致 global 变量状态不可控
Workerman 的每个 Worker 进程启动后长期存活,global 声明的变量在该进程生命周期内持续存在,且不会随请求结束而重置。这意味着:同一进程内多次请求会共享并累积修改同一个 $GLOBALS 键或 global $x 变量,极易造成状态污染。
常见错误现象:MySQL server has gone away 有时不是数据库问题,而是你把 PDO 实例存进 global $pdo 后,连接空闲超时失效,后续请求仍试图复用这个“死连接”。
- 每次请求都可能读到上一次请求残留的值(比如
global $user_id未清空) - 无法保证变量初始化时机 ——
global只是引用,不等于声明或赋值 - 多进程间完全隔离,但单个进程内所有回调(
onMessage、onClose等)共用同一份global空间
global 变量破坏 Workerman 的并发安全模型
Workerman 默认启用多进程(worker_num > 1),但 global 是进程级作用域,不是线程级。表面上看“进程隔离”能避免竞态,可一旦你在某个进程里用 global $counter 做计数,就隐含了“该进程内所有连接共享一个计数器”的假设 —— 而这往往与业务语义冲突(比如想按用户 ID 计数,却按进程 ID 分片)。
更危险的是:若误将对象或资源句柄(如 fsockopen 返回的 socket)存入 global,不同请求可能并发读写同一资源,触发 PHP 层面的 undefined behavior 或底层 I/O 错误。
-
global $cache存数组?多个onMessage回调同时$cache[$key] = $val不加锁,数据会丢失 - 使用
static变量比global稍好,但仍受限于单进程上下文,无法跨进程协调 - 真正需要共享状态(如限流计数)必须走外部存储(Redis)或进程间通信(Unix Socket / 共享内存)
替代方案:用 onWorkerStart + 局部作用域管理资源
Workerman 官方推荐模式是在 onWorkerStart 回调中为每个进程单独初始化依赖,而不是靠 global “一劳永逸”。数据库连接、缓存客户端、配置实例等都应在此处创建并绑定到当前进程上下文。
示例:不要这样写
// ❌ 危险:全局单例,所有进程共享 global $db; $db = new PDO(...);
而应这样写
// ✅ 安全:每个进程独立连接
$worker->onWorkerStart = function($worker) {
$worker->db = new PDO(...); // 挂在 $worker 实例上
};
$worker->onMessage = function($connection, $data) {
$connection->worker->db->query(...); // 显式通过 $connection 或 $worker 访问
};
- 避免在
onMessage中重复 new 对象,但也不要把对象扔进global让所有人随意改 - 若需跨请求暂存数据,优先用
$connection->xxx属性(连接级)或$worker->xxx(进程级),而非global - 所有第三方 SDK(如 Redis 客户端)务必确认其是否支持长连接复用;不支持的请手动封装连接池逻辑
调试时 global 变量会让问题更难定位
当出现“偶发性数据错乱”或“某次请求返回了其他用户的 session 数据”,第一反应往往是查 global 变量是否被意外复用。这类问题无法通过日志直接复现,因为:变量值本身不记录修改栈,debug_backtrace() 在 global 赋值点也只显示最外层调用。
真实场景中,一个 global $config 被 A 模块初始化,B 模块悄悄 $config['timeout'] = 1,C 模块读取时发现超时异常 —— 但 B 和 C 可能根本不在同一调用链上,也没有显式传参关系。
- 静态分析工具(如 PHPStan)对
global变量几乎无能为力,类型推导和副作用检查全部失效 - 单元测试难以 mock
global状态,尤其涉及异步回调时,unset($GLOBALS['x'])未必能还原初始环境 - 线上 debug 基本只能靠
var_dump($GLOBALS)快照,但快照时刻可能已错过污染发生点
真正棘手的从来不是“能不能用”,而是“谁在什么时候改了它”。Workerman 的长生命周期放大了全局状态的不确定性,让原本在短生命周期脚本里可以容忍的写法,变成系统级隐患。别省那几行 $worker->,它比 global 多敲的字符,换来的是一整套可预测、可追踪、可测试的运行时行为。











