协程不适用于cpu密集型任务,因其协作式调度依赖io让出,纯计算无法触发挂起,导致单协程独占cpu、其他协程饿死;唯一可靠解法是启用task_worker_num独立子进程隔离计算。

协程对CPU密集型任务不仅没有加速效果,反而会因调度失控导致服务雪崩——它天生不适合干CPU活。
为什么协程在CPU密集场景下会失效
协程是协作式调度,依赖「主动让出」控制权。但纯计算(比如大循环、矩阵运算、正则回溯)不触发任何IO事件,co::sleep()、co::read()等挂起点根本不会执行,调度器完全无从介入。结果就是:一个协程霸占整个Worker进程的CPU时间片,其他协程饿死。
- 现象:设了
worker_num=8,top 看到 8 个 PHP 进程,但其中 1 个 CPU 占用率 100%,其余全在空转 - 本质:PHP 是单线程解释器,所有协程跑在同一个 OS 线程里,无法并行利用多核
- 误区:以为
go(function () { heavy_calc(); })能“并发执行”,实际只是排队等 CPU,没任何并发收益
task_worker_num 是唯一靠谱的解法
真正能隔离 CPU 密集任务的,只有 task_worker_num。它启动的是独立子进程(非线程),每个 task worker 拥有完整 PHP 解释器和独立内存空间,天然规避单线程瓶颈。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 必须在
swoole_server->set()中显式设置task_worker_num >= 1,设为 0 或不设等于没开 - 投递必须用
$server->task($data),直接调函数或在onReceive里执行等于白配 -
onTask回调里可放心用usleep()、json_decode()、shell_exec(),不会卡主 Worker - 注意:task 进程间不共享
global、static变量,每次都是全新实例
declare(tick=N) 抢占式调度只是理论可行,生产慎用
PHP 的 declare(ticks=N) 确实能实现协程级抢占,但代价极高:每 N 条低级语句就触发一次 handler,严重拖慢计算速度,且 tick 不覆盖所有语句(如类定义、方法声明),边界极难控制。
- 压测显示:1000 万次
$i++在ticks=100下耗时增加约 40%,CPU 效率断崖下跌 - handler 里做时间判断 +
co::yield()会引入额外上下文切换开销,反而放大延迟 - Swoole 官方从未推荐此方案,企业级服务中基本只出现在 PoC 演示里
OpenCPU 或远程计算才是高负载场景的真实出路
当任务复杂度超出单机能力(如模型推理、批量图像处理),硬扛 task worker 已不现实。此时应把计算逻辑外移,用 HTTP/Socket 异步委托给专用计算节点。
- Swoole 端只需用
Swoole\Coroutine\Http\Client发起请求,全程协程安全 - OpenCPU 提供沙箱隔离、资源限制、自动扩缩容,比自建 task worker 更稳更省心
- 关键点:必须设好超时(
timeout)、重试(retry)和降级逻辑,避免计算服务不可用时拖垮网关
最容易被忽略的是:很多所谓“CPU密集”其实是伪命题——比如未加索引的 MySQL 全表扫描、未设超时的 cURL、同步写日志到机械硬盘。先确认真瓶颈再决定上 task 还是换架构,别一上来就堆子进程。










