协程是单线程内协作切换的轻量执行单元,核心价值是避免i/o阻塞进程;go()创建协程后分配栈、存上下文、入就绪队列,遇i/o自动挂起;原生阻塞函数需启用hook替换,context实现协程隔离传值,适用于i/o密集型任务,不提升cpu密集型性能。

协程不是线程,也不是“PHP里的多线程”——它是在单个线程里靠协作切换的轻量执行单元,核心价值是让 I/O 不卡住整个进程。
go() 调用后到底发生了什么?
你写 go(function () { Co::sleep(1); }),Swoole 并不是立刻执行,而是:
- 分配一块默认 8KB 的栈内存(coroutine_t 结构体)
- 把函数指针、参数、当前 PHP 执行上下文(php_context)存进去
- 将该协程加入就绪队列,等事件循环(EventLoop)轮到它时才真正开始跑
- 遇到 Co::sleep()、$client->connect() 这类 I/O 操作时,自动挂起,不占 CPU,也不阻塞其他协程
为什么不能在协程里用 sleep() 或 file_get_contents()?
因为这些是原生阻塞函数,会直接卡死整个 worker 进程——所有协程一起停摆。Swoole 提供了 Hook 机制来解决:
- 必须提前调用 Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL)
- 它会动态替换 PHP 内部函数表,把 sleep() → Co::sleep(),fread() → 协程版读取逻辑
- 但注意:SWOOLE_HOOK_ALL 会 hook 所有支持的函数,部分扩展(如某些 C 扩展自定义的 socket 调用)可能漏掉,导致隐性阻塞
- 常见踩坑:在 onReceive 回调里忘了启用 Hook,结果协程看起来“卡死”了
协程之间怎么传数据?为什么全局变量不行?
协程共享同一进程的内存空间,但每个协程有独立的执行上下文,$_SERVER、$GLOBALS、静态变量这类全局状态会在协程间互相污染。正确方式是:
- 使用 Swoole\Coroutine\Context::set('key', $value) 和 Swoole\Coroutine\Context::get('key')
- Context 是协程隔离的,生命周期与协程一致,协程结束自动销毁
- 别用 global 或 static 存请求级数据(比如用户 ID、trace_id),否则下一个请求进来可能读到上一个协程残留的值
- 特别注意:Context 不跨进程,只在当前 worker 进程内有效;跨进程要用 Redis 或 Swoole\Table
协程适合做什么?不适合做什么?
协程天生为 I/O 密集型任务设计,对 CPU 密集型任务毫无帮助:
- ✅ 适合:HTTP 请求、MySQL 查询、Redis 操作、TCP 通信、文件异步读写
- ❌ 不适合:图像处理、大数组排序、加密解密等纯计算任务——它不会变快,反而因协程调度增加微小开销
- ⚠️ 真实限制:单个 worker 进程是单线程的,协程再多也只用一个 CPU 核;要压满多核,必须配合 Swoole\Process 或 task_worker_num 启动多个进程
- 最容易被忽略的一点:协程没有“超时自动回收”机制,一个没结束的协程(比如死循环或漏掉 Co::sleep())会一直占着栈内存,长期运行服务要警惕协程泄漏










