swoole协程运行于worker进程的单线程内,由用户态调度器控制,非io阻塞点不切换,共享全局变量但协程栈独立;其协作式调度无法并行压cpu,故不适用于cpu密集型任务。

协程不是线程的简化版,也不是“更轻量的线程”——它根本不在操作系统调度层面存在,而线程是内核级实体。用错场景时,协程反而比线程更慢、更难 debug。
协程在 Swoole 里到底跑在哪?
协程运行在 worker 进程的单个线程内,由 Swoole 自己的调度器控制,不经过 OS 调度。每个 go() 启动的协程共享同一个线程栈空间(但有独立协程栈),共用同一套全局变量、静态变量、static 局部变量——这点极易引发竞态。
- worker 进程默认是单线程的(即使你设了
worker_num > 1,每个 worker 仍是单线程) - 协程切换只发生在 IO 阻塞点(如
co::sleep()、$client->recv()、mysql->query()),非阻塞调用不会让出控制权 - 没有
pthread_mutex那类锁机制,但可以用Swoole\Coroutine\Channel或Swoole\Table做跨协程同步
为什么协程不能替代线程处理 CPU 密集任务?
因为协程调度是协作式的:一个协程 if/for/while 算上 10 秒,其他所有协程都得干等。线程则由 OS 强制切片,能真正在多核并行。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 执行
md5_file('/big/file')、imageconvolution()、密集循环等操作时,协程会卡死整个 worker 线程 - 想并行压 CPU,必须用
Swoole\Process启子进程,或投递到task_worker进程中处理 -
set_cpu_affinity()对协程无效——它只对进程/线程生效
协程和线程的错误混用典型现象
常见于数据库连接、Redis 客户端、自定义对象状态管理等场景:
- 在协程中复用
PDO实例:PDO 不是协程安全的,会导致连接错乱或MySQL server has gone away - 用
new Redis()而非Swoole\Coroutine\Redis():前者阻塞整个线程,后者才支持协程挂起 - 在
go()里直接改全局数组$config['timeout']:下一个协程读到的可能是被覆盖的值 - 误以为
Co::getuid()是协程 ID —— 实际它是当前协程在当前 worker 进程内的唯一整数 ID,跨 worker 不唯一,也不能当分布式 trace_id 用
什么时候该选线程而不是协程?
只有两种情况真正需要线程:Swoole\Thread(PHP 8.1+)或通过 pthreads(已废弃)启动的线程,但它们在 Swoole 生态中极少使用,且与协程不兼容。
- 需要调用某些仅提供线程安全 C 扩展的库(如某些图像处理、加密 SDK)
- 必须长期持有某个资源句柄(如硬件设备 fd),且该句柄无法被 epoll 监听或协程化封装
- 注意:
Swoole\Server的task_worker是进程,不是线程;reactor线程是底层网络轮询线程,用户代码不可见、不可干预
真正容易被忽略的是:协程的“无感切换”依赖完整生态支持。只要链路中任意一环(比如你写的某个 SDK、第三方 HTTP client、自研 socket 封装)没做协程 Hook,整个协程就退化为同步阻塞——这时你看到的高并发只是假象,QPS 可能还不如 FPM。









