co函数必须传可调用对象,返回协程ID而非执行结果;推荐用go替代co,避免上下文丢失、异常未捕获及DI容器失效问题。

co 函数是 Hyperf 中启动协程最直接的方式,但它不是“随便包一下就能用”的黑盒。用错地方、传错参数、忽略返回值或上下文,轻则逻辑不执行,重则协程泄漏或内存暴涨。
co 函数必须传可调用对象,不能传普通表达式
常见错误是把 co(function () { ... }) 写成 co($user->save()) 或 co(1 + 1) —— 这会导致 PHP 报 Fatal error: Uncaught TypeError: co() expects parameter 1 to be callable。
- ✅ 正确:传匿名函数、闭包、
[Class, 'method']数组、或实现了__invoke()的对象 - ❌ 错误:传变量、字面量、方法调用结果(如
$obj->foo()) - ⚠️ 注意:如果想在协程里调用异步方法,得确保该方法本身是协程安全的(比如基于
Swoole\Coroutine\MySQL或 Hyperf 的Db组件)
co 启动的协程默认不等待,需手动处理返回值或异常
很多人以为 co(function () { return 'done'; }) 会返回 'done',实际上它返回的是一个 Swoole\Coroutine 对象(协程 ID),不是函数执行结果。
- ✅ 想取返回值:用
Co::wait()+Co::create()组合,或更推荐用go()+Channel/WaitGroup - ✅ 更常用写法:
go(function () { Db::insert(...); });(后台执行,不阻塞) - ⚠️
co不捕获内部异常,未 try/catch 的协程崩溃不会抛到主流程,日志里可能只看到PHP Fatal error: Uncaught RuntimeException而无堆栈
co 和 go 的关键区别:返回值类型与调度语义
co 是 Swoole 原生函数,go 是 Hyperf 封装的语法糖,二者行为高度相似但返回值不同,混用容易出错。
-
co(function () { ... })返回int(协程 ID),不可 await -
go(function () { ... })返回Swoole\Coroutine实例,部分版本支持->wait(),但 Hyperf 官方不推荐依赖此行为 - ? 实际项目中,95% 场景用
go更安全:它自动处理了 Context 传递、异常监听钩子、以及与 DI 容器的生命周期对齐 - ? 别在
go内部再套co,纯属冗余,且可能引发 Context 覆盖问题
协程内访问容器对象要小心 $this 和 DI 上下文丢失
在 co 或 go 启动的匿名函数里,直接写 $this->container->get(...) 会报 Undefined variable: this —— 因为匿名函数没绑定当前对象实例。
- ✅ 正确方式一:用
Container::get()->get(...)(静态访问) - ✅ 正确方式二:用
use ($container)显式导入(前提是 $container 已在外部作用域可用) - ⚠️ 更稳妥做法:把业务逻辑抽成独立服务类,通过
go([Service::class, 'method'])调用,Hyperf 会自动注入依赖 - ? 避免在协程里直接 new Class(),绕过 DI 容器会导致单例失效、AOP 失效、连接池无法复用
真正难的不是怎么调用 co,而是判断“这里到底该不该开协程”——IO 密集型操作才值得协程化,CPU 密集型反而拖慢整体吞吐。很多线上问题,根源是协程开了,但里面跑的是 json_encode 大数组或者 for ($i = 0; $i 这类同步阻塞代码。


















