协程客户端必须在协程上下文中使用,否则报SWOOLE_ERROR_CO_NOT_IN_COROUTINE;on('Request')天然支持,WorkerStart等需用go()或Coroutine::run()显式启动协程环境。

协程必须在协程上下文中才能用
直接在 WorkerStart、Task 或普通 CLI 脚本里 new 一个 Swoole\Coroutine\MySQL,会报 SWOOLE_ERROR_CO_NOT_IN_COROUTINE 错误。这不是代码写错了,是 Swoole 的硬性约束:所有协程客户端(MySQL、Redis、Http\Client 等)只能在已激活的协程栈中运行。
常见错误场景:
- 在
on('WorkerStart')回调里直接 new 协程客户端 - 在
go()外部调用Co::sleep(1) - 用
new Swoole\Coroutine\Redis()但没包在go()或run()里
正确做法:
- 用
Swoole\Coroutine\run()包裹整个逻辑,确保入口就是协程环境 - 或在回调内显式启动:
go(function () { $mysql = new Swoole\Coroutine\MySQL(); ... }); - 注意:
on('Request')是天然协程环境,可直接用,不用套go()
go() 不等于立即并发执行
go() 只是把回调注册进协程调度器,并不保证“马上跑”。它依赖底层事件循环(epoll/kqueue)和当前是否有 IO 阻塞点来触发切换。比如下面这段代码:
go(function () {
Co::sleep(1);
echo "done 1\n";
});
go(function () {
Co::sleep(1);
echo "done 2\n";
});
echo "main exit\n";
输出顺序固定是:main exit → done 1 → done 2(或并行完成,但不会早于 main exit)。因为 go() 启动后主线程继续往下走,Co::sleep(1) 才真正让出控制权。
容易踩的坑:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 误以为
go()启动后就能立刻拿到返回值——实际要等协程结束,得用Channel或wait()同步 - 在
go()里做 CPU 密集计算(如大循环、JSON 解析),会阻塞整个线程,其他协程卡住不动 - 忘记
Co::sleep()、$mysql->query()这类 IO 操作才是协程切换的触发点
单线程 vs 多线程调度模型直接影响锁的必要性
Swoole 协程默认单线程协作式调度,同一时刻只有一个协程在跑,所以全局变量、静态属性、普通数组在协程间天然“隔离”——不是线程安全,而是根本没并发执行机会。但 Go 的 goroutine 是多线程抢占式调度,count++ 这种操作不加锁就会丢数据。
对比示例:
// PHP + Swoole:下面的 $counter 不需要加锁
$counter = 0;
go(function () use (&$counter) {
$counter++;
});
go(function () use (&$counter) {
$counter++;
});
// 最终 $counter 一定是 2
<p>// Go:下面的 count++ 必须加 sync.Mutex,否则大概率是 1 或 2 不确定
var count int
for i := 0; i < 2; i++ {
go func() {
count++
}()
}
关键区别:
- Swoole 协程里,只有遇到 IO 才让出,CPU 密集任务会霸占线程,其他协程饿死
- Go 的 goroutine 会被运行时强制打断,哪怕你在算斐波那契,也会切走,所以更“公平”,但也更危险
- PHP 开发者容易忽略这点:Swoole 项目里混用
pcntl_fork或多 Worker 进程时,全局变量就不再是协程安全的了
协程不是万能的,CPU 密集型任务反而拖垮性能
协程只解决 IO 等待问题,对纯计算毫无帮助。Swoole 协程跑在单线程里,一个协程卡在 hash_hmac('sha256', $bigData, $key) 上,整个服务就卡住,HTTP 请求全积压。
真实项目中容易被忽略的点:
- 图片缩放、视频转码、大文件解析这类操作,别放协程里,应扔给异步 Task 进程处理
- PHP 的
json_decode($hugeJson)在大字符串下耗时显著,建议分块或改用ext-jsond - Go 虽然也有这个问题,但它的抢占式调度至少能切走,而 Swoole 会真·卡死,连心跳都停
- 用
Co::getCid()查当前协程 ID 是调试利器,但别在热路径里频繁调用,有开销
最常被低估的其实是协程栈大小——默认 2MB,1000 个协程就是 2GB 内存。如果业务里递归深、闭包多、对象引用复杂,很容易爆栈或内存暴涨,这时得调 Co::set(['stack_size' => 512 * 1024])。

















