Workerman无法像Nginx那样用配置项绑定CPU核心,因其是PHP用户态框架,未内置worker_cpu_affinity机制,也不调用sched_setaffinity()系统调用;其多进程由fork生成,子进程仅继承父进程全开亲和掩码,后续调度完全交由Linux内核决定,故必须通过taskset在启动时对每个worker进程单独绑定。

Workerman 默认不支持 CPU 亲和性绑定,必须靠 taskset 启动时强制指定,否则所有 worker 进程都可能被调度到同一核心上,白费多核资源。
为什么 Workerman 不能像 Nginx 那样用配置项绑定核心
Workerman 是 PHP 编写的用户态常驻进程框架,它没有内建的 worker_cpu_affinity 类似机制,也不调用 sched_setaffinity() 系统调用。它的“多进程”由主进程 fork() 生成,子进程继承父进程的 CPU 亲和掩码(通常为全开),后续完全交由 Linux 调度器决定运行在哪颗核心——这意味着高并发下极易出现热点核心打满、其他核心空闲的现象。
所以你不能指望改 Worker::$processCount 或加个配置就生效;必须从进程启动层面干预。
用 taskset 启动 Workerman 的正确姿势
直接在命令行用 taskset 包裹 php start.php start 只能绑定主进程,而 fork 出来的 worker 子进程**不会继承亲和性设置**(Linux 默认行为)。必须让每个 worker 进程自己带上绑定逻辑。
- 推荐方式:修改启动脚本,在
Worker::runAll()前插入taskset重执行当前进程(需配合exec()) - 更稳妥方式:用 shell 封装,对每个 worker 启动命令单独加
taskset -c N - 避免踩坑:不要用
taskset -cp动态绑定已运行的 worker 进程——PHP 进程一旦进入事件循环(如EventLoop),再改亲和性可能失效或引发信号处理异常
示例(shell 封装,4 核机器):
#!/bin/bash
# start_with_affinity.sh
CORES=(0 1 2 3)
for i in "${!CORES[@]}"; do
taskset -c "${CORES[$i]}" php start.php start -d --process-name="worker-$i" &
done注意:--process-name 是 Workerman 支持的参数,用于区分进程,方便后续 ps 查看。
怎么验证每个 worker 真的跑在指定核心上
光看 ps 不够,得查内核级亲和掩码:
- 先找 worker 进程 PID:
ps aux | grep 'worker-[0-9]' | grep -v grep - 对每个 PID 执行:
taskset -cp <pid>,输出应类似pid 12345's current affinity list: 2 - 进阶确认:
cat /proc/<pid>/status | grep Cpus_allowed_list,结果应为单个数字(如2),不是范围或多个数字 - 别信
top -H的 “CPU” 列——那是采样瞬间值;要持续观察 30 秒以上,确认该 PID 的 “%CPU” 始终对应固定列号
如果发现多个 worker 显示绑定到同一个 core,说明启动脚本没生效,或用了 fork() 后未重新 exec 导致继承失败。
容易被忽略的 NUMA 和超线程陷阱
在双路服务器或启用了超线程(HT)的机器上,taskset -c 0,1,2,3 看似绑了 4 核,实际可能是 2 物理核 + HT 的 4 逻辑核,缓存竞争反而更严重。更糟的是跨 NUMA node 绑定(比如 core 0 和 core 8 属于不同内存节点),会显著拉高内存访问延迟。
务必先运行:lscpu | grep -E "(CPU\(s\)|Core|Socket|NUMA)",看清物理拓扑。例如输出含 NUMA node(s): 2 和 Core(s) per socket: 8,那就应该优先在 node 0 内选连续物理核(如 0,2,4,6),而不是 0,1,2,3。
另外,Workerman 的 Timer 和 Connection 对象在单核上堆积过多时,即使绑定了核心,也会因 PHP 单线程模型导致瓶颈——此时需配合调高 worker_connections、启用 reuseport(需内核支持)或拆分业务到多个独立 Workerman 实例。


















