go()必须在EventLoop已启动的协程环境中调用,否则因无调度器而报错;它异步注册协程而非立即执行,依赖EventLoop轮询调度,纯CPU循环不会自动让出,需避免阻塞。

go() 必须在协程调度器已启动的环境中调用
直接执行 go() 报错(如 Fatal error: Uncaught Swoole\Error: API must be called in the coroutine),根本原因是:当前 PHP 进程没有运行 Swoole 的事件循环(EventLoop),也就没有协程上下文。go() 不是普通函数,它依赖底层调度器分配栈、注册协程、挂入就绪队列——这些动作只有在 Swoole\Http\Server、Swoole\Coroutine\run() 或 CLI 模式下显式启动的协程环境里才可用。
常见错误场景:
- 在 PHP-FPM / Apache 下直接写
go(function () { Co::sleep(1); });→ 无 EventLoop,直接 fatal - 在
onWorkerStart回调里漏掉go()包裹,直接调Co::mysql->query()→ 该回调不在协程上下文中 - CLI 脚本里只写
go(...)但没用Co::run()或启动 Server → 协程被创建却无人调度,可能立即退出或卡死
go() 启动后不等于“立刻执行”
go() 是异步注册,不是同步调用。它把协程加入调度队列后就返回,主线程继续往下走。协程体何时真正开始执行,取决于事件循环何时轮到它——尤其是当有 I/O 操作(如 Co::sleep()、$client->get())时才会触发挂起与恢复。
这导致两个典型误解:
- 以为
go()返回就能拿到结果 → 实际需用\Swoole\Coroutine\Channel或Co::wait()显式同步 - 以为子协程一定比父协程先输出 → 输出顺序取决于挂起点,比如
co::sleep(0.1)后才让出,而父协程若没阻塞,可能先执行完
协程内仍会卡死:纯 CPU 循环无法自动让出
很多人以为只要进了 go() 就“自动协程化”,其实不然。Swoole 的协程 Hook 只拦截 I/O 函数(如 file_get_contents、curl_exec、sleep),对纯计算型代码(如 for ($i = 0; $i )完全无感知。这种循环会独占当前 worker 线程,阻塞整个协程调度器,所有其他协程都得等它跑完。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
解决办法只有两个:
- 手动插入让点:在长循环中加
Co::usleep(1)或Co::yield()主动让出控制权 - 拆分任务:用
Channel分批处理,每批后挂起,避免单次耗时过长
CLI 脚本里用 go() 容易漏掉 Co::run()
在 CLI 下想单独测试协程逻辑,不能只写 go(...) 就完事。如果没启动调度器,协程注册后不会被执行,脚本可能瞬间退出,什么都没发生。
正确写法分两种:
- 显式启动调度:
Co::run(function () { go(function () { echo "hello"; }); }); - 或更常用:直接用
Co::run()包裹全部逻辑,不用额外go()—— 因为Co::run()本身就在主协程里执行,内部可直接调Co::sleep()等
最易忽略的一点:协程栈默认仅 8KB,递归过深或大数组局部变量容易触发栈溢出,且错误提示未必明确指向栈——遇到莫名崩溃,先查 coroutine_stack_size 配置。

















