协程在发起I/O时自动yield是最核心切换时机;必须经Swoole协程化路径(如Co::readFile或启用SWOOLE_HOOK_FILE等)才能触发调度,原生函数默认阻塞;CPU密集型任务需手动Co::yield()让出控制权。

协程在发起 I/O 时自动让出(yield)
这是最常见、最核心的切换时机。Swoole 的协程调度器不会主动中断正在运行的 CPU 密集型代码,但一旦遇到被 Hook 过的阻塞式 I/O 调用(比如 Co::readFile()、$client->connect()、$redis->get()),底层会立即挂起当前协程,把控制权交还给事件循环,去执行其他就绪的协程。
关键前提是:该 I/O 操作必须走 Swoole 的协程化路径。原生 PHP 函数如 file_get_contents()、curl_exec() 默认不触发切换,除非你提前启用对应 Hook —— 否则它们会直接阻塞线程,整个进程卡住。
-
SWOOLE_HOOK_FILE才能让fopen()/file_get_contents()变成协程安全 -
SWOOLE_HOOK_CURL对应curl_exec() -
SWOOLE_HOOK_SOCKET覆盖stream_socket_client()等底层 socket 操作
协程显式调用 Co::sleep() 或 Co::yield()
当协程里没有 I/O,但你想主动交出 CPU(比如做定时轮询、避免长循环霸占调度器),就得手动让出。这里特别注意:usleep() 和 sleep() 是系统级阻塞调用,会锁死整个线程;而 Co::sleep(1) 是注册一个定时器后立刻 yield,其他协程能继续跑。
同理,Co::yield() 是最轻量的让出方式,不带延迟,只用于协作式同步(比如配合 Co::wait() 或通道通信)。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
-
Co::sleep(0.1)→ 等 100ms 后恢复,期间其他协程可执行 -
Co::yield()→ 立即让出,下次调度器轮到它就恢复 - 不要在
onWorkerStart回调里调用Co::sleep(),此时协程上下文尚未建立
协程执行完毕或发生未捕获异常
协程函数正常 return,或者抛出未被 try/catch 捕获的异常,都会导致该协程终止,调度器自动切到下一个待执行协程。这点和线程不同——协程退出不释放内存资源(比如连接池里的连接),但会清理其栈帧和局部变量。
容易忽略的是:协程内全局变量或静态变量不会随协程销毁而重置,它们属于进程级作用域。如果在协程里改了 $GLOBALS['x'] 或 static $counter,后续协程会看到修改后的值,可能引发状态污染。
- 协程间共享数据推荐用
Co::getContext()或传参,而非全局变量 - 数据库/Redis 连接应从连接池获取,不要复用单例对象
- 异常未 catch 会导致协程静默退出,日志里可能只留一条
PHP Fatal error,不易定位
事件循环空闲时尝试唤醒等待中的协程
当所有协程都处于 I/O 等待或 sleep 状态,事件循环(基于 epoll_wait 或 kqueue)会进入休眠。一旦有事件就绪(比如 socket 可读、定时器到期),调度器就会遍历等待队列,唤醒对应协程。
这个过程不是“抢占”,而是响应式驱动:没有事件,就没有调度动作;有事件,才按优先级和就绪顺序恢复协程。所以协程不会像线程那样被时间片强制打断,但也意味着纯计算逻辑必须自己 yield,否则其他协程永远没机会跑。
- 长时间数学运算、大数组遍历、正则回溯等场景,务必穿插
Co::yield() - 可通过
strace -p $pid -e trace=epoll_wait观察事件循环是否活跃 - 协程卡死往往表现为
epoll_wait长时间不返回,说明没有事件触发,也没有主动 yield
go() 就自动异步,结果发现 QPS 上不去,一查全是 read() 阻塞在 strace 里。

















