协程间变量会互相污染,仅限全局、静态、单例等共享内存;swoole协程共享堆内存,$_server、静态属性等未自动隔离,需用co::getcontext()、co::getuid()键名映射或局部new对象实现隔离。

协程间变量真的会互相污染吗
会,但只发生在全局变量、静态变量、单例对象等跨协程共享的内存区域。Swoole 协程是用户态轻量级线程,共享同一进程的堆内存,$_SERVER、$GLOBALS、静态属性、单例实例这些都没做自动隔离——协程 A 改了,协程 B 下次读就是新值。
常见踩坑场景:
- 在
onReceive回调里给某个静态类属性赋值,后续协程调用该类方法时读到的是前一个协程留下的脏数据 - 用
static $cache = []做“协程内缓存”,实际所有协程共用同一个数组 - 把 PDO 实例存在全局变量里复用,协程切换后执行
->query()可能混掉事务或连接状态
怎么让变量真正“属于当前协程”
必须显式使用协程上下文隔离机制,Swoole 提供了 Co::getContext() 和 Co::setContext(),但更常用且安全的是 Co\Channel 或 Co\WaitGroup 配合局部变量;对需要跨函数传递的上下文数据,推荐用 Co::getUid() 做键名存到 array 中手动管理,或直接用 context 扩展(需编译时开启)。
实操建议:
- 避免在协程中写
static $x,改用函数参数传递或闭包捕获 - 数据库连接、Redis 客户端等资源对象,务必在协程入口处 new,不要复用全局实例
- 若必须用静态缓存,改用
Co::getUid() => $data映射结构,而不是直接存静态数组 -
Co::getContext()返回的是当前协程私有数组,可放心存临时上下文,比如Co::getContext()['user_id'] = 123
为什么 $_SESSION 在 Swoole 协程里不能直接用
因为 PHP 的 session 模块是为 FPM 设计的,底层依赖 flock 文件锁和全局 session_id 状态,协程调度时不会触发 session 写入/关闭逻辑,多个协程并发调用 session_start() 会导致文件锁冲突或覆盖写入,session_destroy() 也可能误删其他协程的 session 数据。
正确做法:
- 完全弃用
session_start(),改用 token + Redis 存储,key 用'session:' . $token,TTL 显式控制 - 若必须兼容旧逻辑,至少把 session 操作包裹进
Co\Channel串行化,但性能差、意义不大 - 注意:Swoole 的
Http\Server不自动解析Cookie: PHPSESSID=xxx,得自己取、校验、加载对应 Redis 数据
协程安全的配置和启动姿势
光写对代码不够,Swoole 启动参数和运行模式直接影响协程变量是否可控。默认 enable_coroutine => true 是开启的,但某些扩展(如 xdebug、pcov)会破坏协程上下文,导致 Co::getUid() 返回 0 或重复。
关键检查点:
- 禁用 xdebug:协程环境下 xdebug 会引发上下文丢失,
Co::getUid()失效,错误日志里可能看到"coroutine id is 0" - MySQLi/PDO 必须启用
mysqlnd驱动,并设置PDO::ATTR_EMULATE_PREPARES => false,否则预处理语句可能跨协程错乱 - Worker 进程数设为 1(
worker_num => 1)有助于调试协程变量问题,排除多进程干扰 - 开启
hook_flags => SWOOLE_HOOK_ALL,确保 file_get_contents、curl 等同步调用被自动协程化,避免阻塞整个协程调度器
协程变量隔离不是语言特性,是靠开发者对内存归属的清醒判断和 Swoole 提供的有限工具共同实现的。最容易被忽略的是扩展兼容性——很多问题根本不在你的代码里,而在 php.ini 加载的某个调试扩展里。











