swoole_process::setAffinity() 是唯一能在 PHP 层显式设置单个进程 CPU 亲和性的 Swoole 原生方法,仅对当前进程生效且须在 fork 后立即调用;swoole_cpu_affinity() 为全局函数,仅作用于调用时的当前进程,无法影响已 fork 的子进程。

直接说结论:swoole_process::setAffinity() 是唯一能在 PHP 层显式设置单个进程 CPU 亲和性的 Swoole 原生方法,但它只对当前进程生效,且必须在子进程创建后、业务逻辑开始前调用;swoole_cpu_affinity() 是全局函数,仅作用于当前 PHP 进程(包括 master 和未 fork 的 worker),无法用于已 fork 的子进程。
为什么 setAffinity() 必须在 fork 后立即调用
Linux 的 CPU 亲和性是进程级属性,继承自父进程。Swoole 的 worker 或 task_worker 进程由 master 进程 fork() 产生,此时子进程初始亲和性与 master 完全一致。如果在子进程里延迟调用 setAffinity(),中间可能已被调度到其他 CPU 上运行,导致缓存预热失效、上下文切换已发生——优化就白做了。
常见错误写法:
// ❌ 错误:在 onWorkerStart 里做耗时初始化后再绑定
$server->on('WorkerStart', function ($server, $worker_id) {
// 这里加载了大量类、连接 Redis、初始化协程池...
// 调度器可能已把该 worker 切到别的 CPU 上跑了几十毫秒
swoole_process::setAffinity([0]);
});
正确时机:
- 在
swoole_process::fork()返回子进程后,第一行就调用setAffinity() - 或在
onWorkerStart回调最开头,甚至早于echo、file_get_contents()等任何系统调用 - 若用
task_worker,必须在onTask内部为每个 task 单独调用(因为 task 是复用的,每次执行前需重置)
数组参数里的数字到底指物理核还是逻辑核
它指 Linux 的「逻辑 CPU 编号」,也就是 /proc/cpuinfo 里 processor 字段的值,不是物理核序号,也不是超线程编号。比如一台 4 核 8 线程的机器,processor 会从 0 到 7,setAffinity([0, 2, 4, 6]) 表示绑定到第 0/2/4/6 号逻辑 CPU —— 它们大概率分布在不同物理核上(避免超线程争抢同一执行单元)。
容易踩的坑:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 误用物理核数做判断:
swoole_cpu_num()返回的是逻辑 CPU 总数(如 8),但setAffinity([4, 5, 6, 7])若全落在同一物理核的两个超线程上,性能反而下降 - 硬编码数组越界:传入
[8]在 8 线程机器上会直接返回false,且触发E_WARNING,但不会抛异常,容易被忽略 - 没校验实际可用性:某些云主机(如 AWS t3/t4g)启用了 vCPU 混合调度,
processor编号不连续或存在隐藏限制,建议先用taskset -c 0 -- echo ok验证
setAffinity() 失败却不报错的三种情况
它返回 false,但默认不抛异常,也不打日志,很多开发者以为“没生效”其实是“调用失败了”。典型场景:
- 进程没有权限:非 root 用户在部分发行版(如 CentOS 7+)默认禁止普通进程修改亲和性,
errno=1(EPERM),需加cap_sys_nice权限或改/proc/sys/kernel/sched_autogroup_enabled - CPU 编号超出范围:比如传
[0, 1, 2, 3, 4]但机器只有 4 个逻辑 CPU(编号 0–3),源码里会直接RETURN_FALSE - 传入空数组:
setAffinity([])立刻返回false,但这是合法调用(表示解除绑定),行为等价于taskset -p 0 <pid>,别误判为失败
建议防御式写法:
if (!swoole_process::setAffinity([0])) {
trigger_error('setAffinity failed, check cpu id and privileges', E_USER_WARNING);
}
和 taskset 命令、swoole_cpu_affinity() 的关系
三者底层都调用 sched_setaffinity(),但作用域完全不同:
-
taskset -cp 0 <pid>:外部命令,可随时修改任意进程,适合调试和临时压测,但无法嵌入 PHP 逻辑流 -
swoole_cpu_affinity(0b0001):只影响调用时刻的当前 PHP 进程,master 进程调用后,后续 fork 出的 worker 仍继承默认亲和性;它用位掩码,0b0001= CPU 0,0b0101= CPU 0 和 CPU 2 -
swoole_process::setAffinity([0, 2]):必须在目标进程内调用,且只对该进程生效,是真正可控的细粒度绑定方式
关键区别在于:如果你要让 8 个 worker 分别绑在 8 个逻辑 CPU 上,swoole_cpu_affinity() 无能为力,必须用 setAffinity() 配合 $worker_id % swoole_cpu_num() 动态计算。
最后提醒一句:CPU 亲和性不是银弹。当你的瓶颈在 MySQL 连接池耗尽、Redis 阻塞调用或协程栈溢出时,绑再准的 CPU 也救不了 —— 先看 swoole_server->stats() 和 strace -p <pid> -e trace=sched_setaffinity 确认是否真卡在调度上。

















