调用 sched_getaffinity(0, sizeof(cpuset), &cpuset) 可获取当前线程的 CPU 亲和性,其中 pid=0 表示调用者自身线程,cpusetsize 必须为 sizeof(cpuset),且需先用 CPU_ZERO 初始化掩码。

如何用 sched_getaffinity 获取当前线程的 CPU 亲和性
直接调用 sched_getaffinity 即可,但必须注意:它操作的是「进程 ID」或「线程 ID」,而 Linux 下线程在内核中本质是轻量级进程(LWP),所以传 0 表示当前线程 —— 这是最常用也最容易被忽略的关键点。
典型用法:
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == -1) {
perror("sched_getaffinity");
return -1;
}
// 此时 cpuset 已填充当前线程实际绑定的 CPU 位图
-
0是 magic value,表示调用者自身线程(不是 getpid() 或 gettid() 的返回值) -
sizeof(cpuset)必须传准确大小,否则可能读取越界或截断(glibc 会检查并返回EINVAL) -
CPU_SETSIZE是编译时最大支持 CPU 数(通常是 1024),但cpu_set_t实际大小由系统头定义,不能硬写成sizeof(cpu_set_t)—— 它是不透明类型,sizeof可能随内核配置变化;稳妥做法就是用sizeof(cpuset)
为什么 sched_getaffinity 返回的 CPU 集可能比物理核心数还小
这不是 bug,而是受多层约束叠加影响的结果:
- 启动时通过
taskset或numactl设置的进程级亲和性会继承给线程 - 容器环境(如 Docker、Kubernetes)默认通过 cgroups v1 的
cpuset.cpus或 v2 的cpuset.cpus.effective限制可见 CPU,sched_getaffinity返回的是「当前生效的交集」,不是硬件拓扑 - 如果进程被
pthread_setaffinity_np显式设置过,且未重置,也会覆盖默认值 -
sysctl kernel.sched_smt_power_savings=1等节能策略可能导致部分逻辑核被 runtime 隐藏(尤其在旧内核上)
验证方法:对比 cat /proc/self/status | grep Cpus_allowed 和你的 sched_getaffinity 结果,二者应一致 —— 若不一致,说明代码里某处修改了亲和性但没检查返回值。
立即学习“C++免费学习笔记(深入)”;
sched_getaffinity 在多线程程序中的线程安全与陷阱
函数本身是异步信号安全的,但使用方式极易出错:
- 多个线程并发调用
sched_getaffinity(0, ...)没问题,各自拿到自己的亲和性 - 但如果用
gettid()获取 tid 后传给sched_getaffinity,必须确保该线程尚未退出 ——gettid()返回的值在内核中可能已被复用,导致查到的是另一个线程甚至进程的亲和性 -
cpu_set_t是栈变量,别跨线程传递指针;更别把它当成全局缓存反复用 ——CPU_ZERO必须每次调用前重置,否则残留位会干扰判断 - 在
std::thread构造后立即获取亲和性?不一定可靠:C++ 标准不保证线程函数何时开始执行,中间可能被调度器迁移。真正稳的方式是在线程函数入口第一行就调用sched_getaffinity
如何把 cpu_set_t 转成可读的 CPU 列表(比如 "0,2,4-6")
没有标准库函数做这个转换,得手撸解析逻辑。关键点在于:不能只遍历 CPU_SETSIZE,而要结合 sysconf(_SC_NPROCESSORS_ONLN) 获取当前在线 CPU 上限,避免扫描无效位:
int ncpus = sysconf(_SC_NPROCESSORS_ONLN);
for (int i = 0; i < ncpus; i++) {
if (CPU_ISSET(i, &cpuset)) {
// 收集连续区间,合并为范围表达式
}
}
-
CPU_ISSET(i, &cpuset)是唯一安全的查询方式,不要直接操作cpuset.__bits[]—— 字段名和布局是 ABI 内部细节,不同 glibc 版本可能变化 - 某些嵌入式系统或实时内核可能把
CPU_SETSIZE设得很小(如 32),此时sysconf(_SC_NPROCESSORS_ONLN)可能大于CPU_SETSIZE,这时sched_getaffinity本身就会失败(ERANGE),需动态分配足够大的cpu_set_t缓冲区(用syscall(SYS_sched_getaffinity, ...)+malloc)
真正难的从来不是调用函数,而是理解「谁在控制这个掩码」—— 内核调度器、cgroup、启动参数、父进程、甚至 BIOS 的 C-state 设置,都可能静默改写它。每次看到亲和性不符合预期,先看 /proc/self/status,再查 cgroup,最后才怀疑代码。


















