Workerman CPU 100% 通常是单个 Worker 进程死循环或同步阻塞所致,可通过 strace -tt -p PID 观察 epoll_pwait 是否密集返回来快速定位;常见原因包括回调中 while(true) 无休眠、误用同步函数、未启用协程 Hook、Task/Process 逻辑缺陷等。

Workerman CPU 100% 时,大概率不是服务器整体过载,而是某个 Worker 进程在死循环或同步阻塞 —— 直接看 strace -tt -p PID 输出里有没有密集的 epoll_pwait 就能快速验证。
用 strace 看进程到底在忙什么
Workerman 是事件驱动模型,正常情况下大部分时间都在 epoll_pwait 上挂起等待 I/O。如果 strace 每秒刷出几十上百行 epoll_pwait 返回(尤其返回值是 0 或超短超时),说明它没等到事件就立刻重试,本质是“空转”。
常见触发点:
-
onMessage、onConnect、onClose回调里写了while(true)且没sleep或Coroutine::sleep - Task Worker 中用了
usleep(1)而不是协程版Coroutine::sleep(0.001) - 自定义
AbstractProcess类里漏了休眠,或休眠时间设成 0 - 错误地在协程中调用了阻塞函数,如
file_get_contents、curl_exec(未启用协程 Hook)
确认是不是业务逻辑卡在同步操作上
Workerman 的 Worker 进程一旦执行同步阻塞调用,整个进程就卡住,无法处理其他连接,CPU 反而可能不高;但若阻塞后又强行轮询(比如自己实现 busy-wait),CPU 就会拉满。
检查重点:
- 所有
onMessage回调里是否含file_get_contents、shell_exec、mysqli_query等非协程函数 - 是否启用了 Swoole 的协程 Hook:
Swoole\Runtime::enableCoroutine(true)(必须在Worker::runAll()前调用) - 数据库/Redis 客户端是否用了协程版本(如
swoole_mysql、hyperf/redis),而非phpredis或PDO - 日志写入是否用了同步文件追加(
file_put_contents(..., FILE_APPEND)),建议换Swoole\Coroutine\WriteFile或异步日志库
别忽略 Task Worker 和自定义 Process
很多人只盯着 Websocket 或 HTTP Worker,但 CPU 100% 往往出在后台任务里。
排查动作:
- 用
ps aux | grep task找出 Task Worker 的 PID,单独strace它 - 检查
Worker::$taskWorkerCount是否设得过大,导致空跑进程过多 - 自定义
AbstractProcess子类中,handle()方法必须包含可控休眠,不能裸写while(true) - 如果用了
pcntl_fork手动派生子进程,确认子进程退出逻辑是否健全,避免僵尸进程堆积
监控和复现要配合着来
Workerman 自带的状态页(默认 :5555)只能看连接数和吞吐,对 CPU 根源无感。真正有用的是运行时快照:
- 用
pidstat -u -p PID 1区分用户态(%usr)和内核态(%system)占比:如果%usr接近 100%,基本锁定是 PHP 层死循环 - 临时在关键回调开头加
echo "start @ " . microtime(true) . "\n";,结尾加echo "end @ " . microtime(true) . "\n";,观察是否“有始无终” - 压测时不要只看平均响应时间,盯住
99% line—— 如果它突然飙升且伴随 CPU 拉满,大概率是某次回调卡死拖垮了整个 Worker
最易被忽略的一点:Workerman 的 Worker 进程默认不自动重启,一个进程一旦进入死循环,就会持续占用一个 CPU 核心,直到你手动 kill —— 所以 restart 脚本里最好加上 --force 和进程存活检测。

















