Swoole协程与多线程本质不同:协程是用户态协作式调度,仅IO阻塞时切换,共享全局变量但栈独立,不适用CPU密集任务;线程由OS抢占式调度,可真并行压多核,适合计算密集型场景。
要搞清Swoole协程与多线程在真实生产环境里到底怎么选、为什么不能混用,必须直击调度机制、内存模型和错误爆发点这三个不可绕过的硬核差异。
协程由 Swoole 用户态调度器控制,仅在 IO 阻塞点(如 co::sleep()、$mysql->query()、$redis->get())主动让出 CPU;非阻塞代码(比如 for($i=0;$i)会霸占整个 worker 线程,其他所有协程全部卡死。
线程由操作系统内核调度器强制切片,不管你在干啥,时间片一到立刻暂停,换另一个线程执行——哪怕你正做 MD5 计算、图像卷积,也能被硬生生打断。
【协程不会自动让出 CPU,纯计算不触发切换】 这是绝大多数协程卡死问题的根源。
所有协程运行在同一个 worker 进程的单线程内,共用同一份全局变量、static 局部变量、类静态属性。你在协程 A 里执行 $config['timeout'] = 30,协程 B 下一秒读到的很可能就是这个被覆盖的值。
线程间也共享进程内存,但操作系统提供 pthread_mutex、信号量等原语来加锁保护;而 Swoole 协程层没有原生互斥锁,必须用 SwooleCoroutineChannel 或 SwooleTable 手动实现同步。
协程栈独立,但“身体”(全局状态)是共用的——这不像线程那样有明确的同步契约,极易引发竞态。
方法一:IO 密集型任务(HTTP 请求聚合、Redis 批量读写、MySQL 查询编排)→ 必用协程。实测单核 1G 机器上并发 5000 次百度请求耗时约 1.8 秒,QPS 超 2700。
方法二:CPU 密集型任务(md5_file()、imageconvolution()、密集数学循环)→ 协程完全失效,必须扔进 task_worker 进程或用 SwooleProcess 启子进程处理。
方法三:调用非协程安全的扩展(如原生 PDO、new Redis())→ 直接复用会导致连接错乱、“MySQL server has gone away”,必须改用 SwooleCoroutineMySQL 或 SwooleCoroutineRedis。
第一步:启动一个 Swoole Server,设置 worker_num = 4
第二步:用 ps aux | grep php 查看进程列表 → 你会看到 4 个 worker 进程,每个 PID 对应一个 OS 进程
第三步:进入任一 worker 进程,执行 cat /proc/[pid]/status | grep Threads → 显示 Threads: 1
第四步:在该 worker 内用 go() 启 1000 个协程 → top -Hp [pid] 查看线程数,仍是 1
协程对操作系统完全透明,它只是 Swoole 在单线程内维护的一组可挂起/恢复的执行上下文;而线程是内核级实体,pthread_create() 会真实增加 Threads 计数。