co::yield()必须在协程内调用,否则报Fatal error;需先用go()启动协程再调用,且必须配对co::resume($cid)恢复,否则导致协程泄露。

co::yield() 必须在协程内调用,否则报错
直接在主协程外(比如 onWorkerStart 回调里)调用 co::yield() 会触发 Fatal error: Uncaught Swoole\Error: yield(): not in coroutine。这不是语法问题,而是运行时上下文缺失——Swoole 要求当前 PHP 执行栈必须处于一个已启动的协程中,才能保存上下文并挂起。
正确做法是:先用 go() 或 Swoole\Coroutine::create() 启动协程,再在该闭包内调用 co::yield()。
-
go(function () { co::yield(); });✅ -
co::yield();在$server->on('Request', ...)外层直接写 ❌ - 注意:
co::yield()不会自动恢复自己,必须由其他协程显式调用co::resume($cid)
co::resume() 的 $cid 参数必须有效且未结束
co::resume($cid) 的参数是目标协程的 ID,这个 ID 必须来自 go() 返回值或 co::getCid(),且该协程当前状态必须为 STATUS_SUSPENDED(即刚被 yield() 挂起)。如果传入无效 CID、协程已执行完毕、或已被调度器回收,co::resume() 会静默失败,不报错也不生效。
常见误用:
- 保存了 CID,但目标协程早已退出,再 resume 无效
- 把
go()返回的整数当对象用,比如co::resume($coro->cid)——go()返回的是 int,不是对象 - 在协程 A 中
co::yield(),却在协程 B 中用错误的 CID 调用co::resume(),导致 A 永远卡住
yield/resume 不是线程唤醒,没有锁或同步语义
co::yield() 和 co::resume() 是纯协作式控制权转移,不带任何内存可见性、原子性或竞态保护。如果你在两个协程间通过共享变量传递状态(比如用全局数组存 flag),必须自行保证读写顺序,Swoole 不做任何同步干预。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
典型陷阱:
- 协程 A 写
$shared['ready'] = true后co::yield() - 协程 B 看到
$shared['ready'] === true就co::resume($cidA) - 但 PHP 的变量赋值和 yield 之间无内存屏障,B 可能读到旧值,或 A 的写还没刷出——这不是 bug,是协程模型本身的设计约束
- 真正安全的做法是用
Swoole\Coroutine\Channel或Swoole\Coroutine\WaitGroup替代裸变量通信
协程泄露:yield 后没 resume,也没超时清理
每次 co::yield() 都会让协程进入挂起态并占用内存(约 8KB 栈空间),如果永远没人调用 co::resume(),这个协程就“泄露”了——它不会自动 GC,也不会被调度器回收,直到 Worker 进程重启。
生产环境必须防范:
- 避免无条件
co::yield();总要配套超时机制,比如用co::sleep(30)替代无限等待 - 用
Swoole\Coroutine::stats()定期检查suspended_num,异常增长就是泄露信号 - 不要依赖“协程退出后自动 resume 其他协程”——Swoole 不提供这种隐式联动
手动 yield/resume 是底层控制手段,日常开发中更推荐用 Channel、WaitGroup 或 await 风格的协程客户端,它们内部已封装了安全的挂起/恢复逻辑,不容易漏掉 cleanup。

















