协程结束时Context自动释放,无需手动调用del;未真正结束的挂起协程(如无限sleep、长连接、阻塞IO、异常卡住)会导致Context持续驻留,而静态变量误用或上下文透传缺失是“看似未释放”的主因。

协程结束时自动释放,不是靠手动清理
Context 数据的生命周期完全绑定当前协程 ID,只要协程执行完毕(execute() 返回、协程内逻辑自然退出、或被 Coroutine::kill() 终止),其关联的 Context 就会被框架自动清空。你不需要、也不应该在业务代码里写 Context::del() 来“主动释放”——除非你明确知道该数据可能被后续同协程内逻辑误读(比如中间件提前 set 了临时标记,控制器又没用上)。
哪些情况 Context 不会释放?
常见于协程未真正结束却长期挂起:
- 协程中调用了
sleep()、Coroutine::sleep()或阻塞式 IO(如未封装成协程客户端的fread()),协程还在运行,Context 持续存在 - 定时任务里用了
while (true) { ... Coroutine::sleep(60); }这类无限循环,协程永不退出,Context 一直驻留 - WebSocket 连接协程中,用户长连接未断开,Context 随连接存活数小时甚至数天
- 异常未抛出导致协程卡在某处:比如 Redis pipeline 中抛了异常但没
finallyreset,协程卡在等待响应,Context 滞留
Context::set() 后不 unset() 会泄漏内存吗?
不会。PHP 协程上下文底层是基于 Coroutine::getContextFor($cid) 的弱引用映射,协程终止后整个 Context 数组被 GC 回收,无论你 set 过多少次、是否 unset。但要注意:
- 如果 set 的值本身持有大对象(如一个 10MB 的数组、未关闭的文件句柄),而该对象又被其他变量引用,GC 就无法及时回收——问题不在 Context,而在你 hold 住了不该 hold 的资源
- 静态变量或全局变量里存了 Context 数据(如
static $cache = [];),那确实会泄漏,但这已脱离 Context 机制本身 - 在父协程 set 的数据,子协程默认不继承;若需传递,必须显式
Context::set(..., $coroutineId),否则子协程看到的是空 Context
为什么有时 Context 看起来“没释放”?
最常被误判的情况是:协程 A 结束了,但你在协程 B 里用 Context::get() 拿到了 A 留下的值。这说明你用了共享状态,比如:
- 误把
static $user_id当 Context 用,结果多个协程共用同一静态变量 - 中间件里
Context::set('request', $request)后,又在服务层直接修改了$request对象属性(非不可变操作),导致后续协程读到脏数据 - 异步任务中没做上下文透传:
go(function () { ... });启动的新协程没有继承父协程 Context,但你误以为它有
真正的 Context 隔离是严格的,所谓“没释放”,90% 是代码里混用了静态/全局变量,或者上下文传递漏了步骤。


















