Swoole协程容器内部与外部的区别
Swoole协程容器外代码同步阻塞,容器内协程调度器接管输入输出操作实现异步。进入方式包括使用Corun函数、启用协程功能的服务器或进程。父子协程默认深度优先调度,全局变量不隔离,跨协程通信需使用通道。该机制让开发者以同步风格编写高效异步代码。
在 Swoole 的世界里,“协程容器”这个概念就像一个开关,决定了代码运行在哪种模式下。弄不清楚这一点,代码出了奇怪的问题都不知道往哪查。
简单来说,容器外面的代码永远是同步阻塞的,走的是传统 PHP 那一套。只有进了容器,Swoole 的协程调度器才开始工作,你才能用上 go()、co::sleep() 这些 API。不进容器,协程就根本没机会跑起来。

协程容器外:没有调度器,只有普通 PHP 执行流
所有在 Corun() 外、SwooleHttpServer 启动前、或者没有启用 swoole.enable_coroutine=1 的代码,都还活在经典的 PHP-FPM 模式里。这就意味着:
- 写个
sleep(1),进程真就实打实地卡住一秒,期间什么也干不了 curl_exec()、mysqli_query()这些 IO 操作,全程阻塞,别指望它们能自动变协程- 强行调用
go()?只会报个Warning: go(): coroutine is not a vailable给你看 - 即使你好奇地试试
co::getCid(),返回的也是0——系统明确告诉你:当前不在协程中
协程容器内:协程调度器接管执行权
只有进入容器,Swoole才会启动它的协程调度器。此时,发起IO操作时,系统能把当前协程挂起,把执行权让给别的协程,等IO完成再回来继续。常见的进入方式有这么三种:
Corun(function () { ... })—— 最直接的“裸写”方式,CLI 脚本或单元测试里很常用SwooleHttpServer启动时设置enable_coroutine => true,每个请求的回调会自动进入协程上下文SwooleProcess或SwooleProcessPool构造时传入['enable_coroutine' => true],子进程也因此自带协程环境
这里有个细微的差别:Corun() 是一次性容器,跑完就退出;而 Server 或 Process 是长期运行的容器,协程可以在里面反复创建、销毁。
父子协程优先级与执行顺序容易被误解
很多人以为 go() 就是“并发执行”,其实它的默认调度方式是“深度优先”:子协程会先跑,直到它第一次主动让出(yield),比如调用了 co::sleep(0.1),才切回父协程。一个经典陷阱就能说明问题:
Corun(function () {
echo "parent start\n";
go(function () {
echo "child 1 start\n";
co::sleep(0.2);
echo "child 1 end\n";
});
go(function () {
echo "child 2 start\n";
co::sleep(0.1);
echo "child 2 end\n";
});
echo "parent end\n";
});
输出的顺序是:parent start → child 1 start → child 2 start → parent end → child 2 end → child 1 end。这不是并行的时间线,而是调度器按照创建顺序,并结合 yield 的时机来决定谁先恢复执行。
全局变量和静态属性在容器内外表现完全不同
容器外面,static $x = 0 或 global $y 是进程级别共享的。但容器内,每个协程都有自己独立的调用栈,这些变量并不会自动隔离。这意味着:
- 多个协程同时修改同一个
static变量,结果根本无法预测(没有锁来保护) go(function () use ($data) { ... })是安全传递参数的方式,因为$data是通过值拷贝传进去的- 想跨协程通信,必须老老实实使用
Swoole\Coroutine\Channel,别想着靠全局变量来搞定
最容易被忽略的一点是:协程容器不是“魔法沙盒”,它不会自动帮你隔离内存上下文。开发者必须自己管理数据边界,否则在高并发下,状态污染的危害比传统 FPM 模式更隐蔽,也更难复现。这才是协程编程中最容易踩的坑之一。


































