协程堆积是cpu飙高的主因,需用coroutine::count()和swoole\timer::count()实时监控,结合top -h -p定位线程热点;持续>2000且不回落即泄漏,重点查闭包持$this或redis连接池未释放。

直接看 Coroutine::count() 和 Swoole\Timer::count(),再结合 top -H -p 查线程级热点,基本能锁定 80% 的高 CPU 场景。不是配置问题,绝大多数是代码里漏了挂起或阻塞调用。
怎么看协程是否堆积
协程不释放是 CPU 飙高的头号原因,尤其在自定义进程、定时任务或长连接推送逻辑里。别只信日志,得实时数:
- 加一行定时打印:
Swoole\Timer::tick(10000, fn() => printf("[CORO] %d\n", Coroutine::count())); - 健康值参考:
Coroutine::count()持续 > 2000 且不回落,基本可断定泄漏 - 重点检查闭包中是否持有
$this或未关闭的 Redis 连接池实例——它们会让整个对象生命周期卡在协程栈里 - 用
Coroutine::listCoroutines()导出堆栈(注意:生产环境慎用,会短暂卡住事件循环)
为什么 Swoole\Process 一启动就 CPU 70%
这是最典型的“空转陷阱”。Swoole\Process 的回调函数默认是同步阻塞模型,一旦写个 while(true) 却没 sleep 或 co::sleep,就会把绑定的线程吃满。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 错误写法:
new Swoole\Process(function($proc) { while(true) { /* 无休眠逻辑 */ } }); - 正确写法:必须加休眠,且优先用协程版:
Coroutine::sleep(1)(不是sleep(1),后者会阻塞整个进程) - 如果 Process 里要轮询 Redis 队列,记得加
$redis->llen()判断长度再 pop,避免空轮询 - 该问题在 Swoole 6.1.1 中仍未修复,属于设计约束,不是 bug
怎么确认是不是阻塞式 IO 在作祟
协程环境里调用 file_get_contents、curl_exec、同步 PDO 查询,会直接让 Worker 进程停摆,CPU 却显示 100%——因为事件循环被卡死,调度器疯狂重试。
- 开 debug 日志:
ini_set('swoole.log_level', SWOOLE_LOG_DEBUG),搜WARNING: blocking关键字 - 用
strace -p <pid> -e trace=connect,read,write</pid>看系统调用是否长时间阻塞 - 替换原则:所有 I/O 操作必须走协程客户端,例如
Swoole\Coroutine\Redis、Swoole\Coroutine\Http\Client - 特别注意
var_dump、print_r在高频回调(如onRequest)里也会引发可观 CPU 开销
worker_num 设太大反而更耗 CPU
很多人以为多开 Worker 就能扛并发,结果发现 CPU 更高、响应更慢。本质是进程间调度抖动 + 内存竞争加剧。
- 先执行
nproc确认物理核心数,比如返回8,那worker_num最高设到16(仅限 IO 密集型) - 用
ps aux | grep php数真实 worker 进程数,排除 supervisor 或 systemd reload 失败导致的旧配置残留 - 若业务含图像处理、大文本解析等 CPU 密集操作,
worker_num应 ≤ 物理核数,否则上下文切换开销反超收益 -
task_worker_num不为 0 时,务必确保投递任务后有$server->finish()或异常兜底,否则 task 进程卡住会拖垮整个 manager 调度
真正难定位的是混合型问题:比如一个协程里既做了未超时的 Redis 查询,又调了同步 curl,还顺手 file_put_contents 写了日志——这种组合拳会让 CPU 曲线毫无规律,必须逐层关掉非核心逻辑做减法验证。监控不能只看平均值,要看 P95/P99 的协程数和内存增长斜率。










