sched_setaffinity 设置进程或线程允许运行的 CPU 核集合,内核调度器仅在该掩码指定的核内调度,不强制固定单核;多核掩码下仍可能迁移,子进程继承但 exec 重置,默认不独占,需配合 IRQ 亲和性与 RT 限流调整。

什么是 sched_setaffinity,它到底在设什么
它设置的是进程(或线程)能运行在哪几个 CPU 核上,不是“绑定到某核”,而是“允许运行的核集合”。内核调度器会尊重这个掩码,在该集合内做调度,但不会强制轮转或固定到单个核——除非你只设了 1 位。
常见误解是认为设了亲和性就等于“独占”或“不迁移”,其实只要掩码里有多个 bit,进程仍可能在这些核之间迁移(比如负载均衡触发),只是不会跑到掩码外的核上去。
实操建议:
- 用
taskset -c 0,2,4-6 ./myapp启动时指定,比运行后调sched_setaffinity更可靠(避免启动瞬间被调度到其他核) - 子进程默认继承父进程的亲和性掩码,但 fork 后 exec 会重置为系统默认(除非显式保留)
- 检查当前值:读
/proc/中的/status CPUS_allowed_list字段,比taskset -p更准确(后者可能因线程数多而截断)
为什么 pthread_setaffinity_np 在多线程程序里容易失效
因为它是 per-thread 的,而很多线程库(如 glibc 的 pthread)在创建新线程时,会把亲和性重置为继承自创建者线程的值——但这个“继承”发生在 clone() 阶段,早于你调用 pthread_setaffinity_np 的时机。结果就是:你设了,但新线程一启动就又被覆盖。
更隐蔽的问题是,某些线程池(如 libuv、boost::asio)内部会主动调用 sched_setaffinity(0, ...) 清除掩码,以保证工作线程可跨核调度。
实操建议:
- 在线程函数入口第一行立刻调用
pthread_setaffinity_np,不要依赖构造时设置 - 若用 C++ std::thread,需在 lambda 或函数对象里手动设,不能靠
std::thread构造参数传递 - 验证是否生效:在目标线程里读
sched_getaffinity(0, sizeof(mask), &mask),别只信启动命令
硬中断(IRQ)亲和性和进程亲和性冲突怎么办
网卡收包中断默认可能落在 CPU 0 上,而你的高性能服务绑在 CPU 3–7;结果软中断(ksoftirqd)虽然能迁移到服务所在核,但硬中断处理仍卡在 CPU 0,造成缓存失效和锁竞争。这不是进程亲和性没设好,而是 IRQ 没对齐。
/proc/irq/ 控制硬中断分发,它和进程亲和性是两套独立机制,互不感知。
实操建议:
- 查网卡 IRQ 号:
grep eth0 /proc/interrupts | awk '{print $1}' | tr -d ':' - 设 IRQ 到同组 CPU:
echo "3-7" > /proc/irq/(注意:需 root,且部分内核版本要求写十六进制掩码)/smp_affinity_list - 确认生效后,再观察
cat /proc/interrupts对应列的计数是否只在目标核上增长 - 如果用了 RPS/RFS,也要同步配
/sys/class/net/eth0/queues/rx-0/rps_cpus,否则软中断仍不均衡
实时任务(SCHED_FIFO)+ 亲和性组合下的陷阱
很多人以为“绑核 + 实时调度 = 稳定低延迟”,但漏掉关键一点:Linux 实时调度器默认启用 sysctl kernel.sched_rt_runtime_us 限流(默认 950ms/1s),一旦实时任务跑满,就会被强制 throttled,表现为周期性卡顿,且 top 不显示 CPU 占用率高——因为时间片被掐断了。
此时即使亲和性正确、无其他进程干扰,也会出现毫秒级抖动。
实操建议:
- 先关限流:
echo -1 > /proc/sys/kernel/sched_rt_runtime_us(生产环境慎用,需确保实时任务绝对可控) - 确认
ulimit -r允许的最高优先级足够(默认常为 0,需ulimit -r 99) - 用
chrt -f -p 99设置策略和优先级,仅设亲和性不改调度类毫无意义 - 真正压测时,用
perf sched latency查看调度延迟直方图,别只盯平均值
最易被忽略的是:IRQ 亲和性、RT 限流、线程继承行为这三者叠加时,问题现象会相互掩盖。调试顺序错了,花半天也找不到根因。










