将线程绑定到指定 CPU 核

前言

在多核 CPU 环境下,进程和线程通常会被调度到不同的核心上运行。为了减少调度开销、提升实时性或性能一致性,可以通过 sched_setaffinity 将某个进程或线程固定在指定的 CPU 核心上运行

函数原型

#include <sched.h>

int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);

pid

  • 0:表示当前进程/线程

  • 其他:指定目标进程或线程的 PID/TID

cpusetsize

  • 一般为 sizeof(cpu_set_t)

mask

  • CPU 集合,指定允许运行在哪些核心上

使用流程

定义并清空 CPU 集合

cpu_set_t mask;
CPU_ZERO(&mask);

设置需要绑定的 CPU

CPU_SET(0, &mask); // 绑定到 CPU0
CPU_SET(2, &mask); // 也可以同时绑定多个核

调用函数

sched_setaffinity(0, sizeof(mask), &mask); // 0 表示当前线程

线程级绑定

在多线程程序中,pthread_self() 返回的并不是系统线程号,无法直接使用。 需要通过系统调用获取 TID:

#include <sys/syscall.h>
#include <unistd.h>

pid_t tid = syscall(SYS_gettid); // 获取当前线程的 TID
sched_setaffinity(tid, sizeof(mask), &mask);

具体例子

示例代码: test.cpp

  • 主线程不绑定(由系统自由调度)。

  • 创建 2 个工作线程:一个绑到 CPU0,另一个绑到 CPU1。

  • 每个线程启动后打印自己的 TID 和当前所在 CPU(通过 sched_getcpu())。

// test.cpp
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <cstring>
#include <cerrno>

#include <sched.h> // sched_setaffinity, cpu_set_t
#include <unistd.h> // syscall
#include <sys/syscall.h> // SYS_gettid

// 将当前线程绑定到指定 cpu_id
bool bind_current_thread_to_cpu(int cpu_id) {
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(cpu_id, &mask);

pid_t tid = (pid_t)syscall(SYS_gettid);
if (sched_setaffinity(tid, sizeof(mask), &mask) != 0) {
std::cerr << "[ERROR] sched_setaffinity: " << std::strerror(errno) << "\n";
return false;
}
return true;
}

void worker(int cpu_id, const char* name) {
// 绑定
if (!bind_current_thread_to_cpu(cpu_id)) {
std::cerr << name << " failed to bind to CPU" << cpu_id << "\n";
return;
}

// 打印一次绑定信息,并持续打印当前所在 CPU 验证效果
pid_t tid = (pid_t)syscall(SYS_gettid);
std::cout << name << " (TID=" << tid << ") bound to CPU" << cpu_id << "\n";

for (int i = 0; i < 10; ++i) {
// sched_getcpu() 返回当前线程正在运行的逻辑 CPU 编号
int cur = sched_getcpu();
std::cout << name << " (TID=" << tid << ") running on CPU " << cur << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
}

int main() {
// 启动两个线程分别绑到 0 和 1 号 CPU
std::thread t0(worker, 0, "worker-0");
std::thread t1(worker, 1, "worker-1");

// 主线程不绑定,打印它当前所在 CPU(可能变化)
for (int i = 0; i < 5; ++i) {
int cur = sched_getcpu();
std::cout << "main thread running on CPU " << cur << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

t0.join();
t1.join();
return 0;
}

说明

  • 线程级绑核的关键:获取 TID(syscall(SYS_gettid))并传给 sched_setaffinity。

  • sched_getcpu() 是 Linux 的便捷函数,用来查看当前线程正在运行的 CPU。

  • 若你的机器只有 1 个逻辑核,记得把 CPU_SET(1, …) 改成 0 或用 lscpu 先查看核数。

编译

g++ -O2 -pthread test.cpp -o pin
  • 必须加 -pthread,否则多线程与相关符号链接可能不完整。

  • 如果你使用 CMake,也可以在 target_link_libraries 里链接 pthread。

运行与实时观察

方式 A:直接前台运行,看程序自身输出

./pin

输出:

~ » ./pin 
main thread running on CPU 1
worker-1 (TID=896669) bound to CPU1
worker-1 (TID=896669) running on CPU 1
worker-0 (TID=896668) bound to CPU0
worker-0 (TID=896668) running on CPU 0
worker-1 (TID=896669) running on CPU 1
worker-0 (TID=896668) running on CPU 0
main thread running on CPU 1
worker-1 (TID=896669) running on CPU 1
worker-0 (TID=896668) running on CPU 0
worker-1 (TID=896669) running on CPU 1
worker-0 (TID=896668) running on CPU 0
main thread running on CPU 1
worker-1 (TID=896669) running on CPU 1
worker-0 (TID=896668) running on CPU 0
main thread running on CPU 1
worker-1 (TID=896669) running on CPU 1
worker-0 (TID=896668) running on CPU 0
worker-0 (TID=896668) running on CPU 0
worker-1 (TID=896669) running on CPU 1
main thread running on CPU 1
worker-0 (TID=896668) running on CPU 0
worker-1 (TID=896669) running on CPU 1
worker-0 (TID=896668) running on CPU 0
worker-1 (TID=896669) running on CPU 1
worker-0 (TID=896668) running on CPU 0
worker-1 (TID=896669) running on CPU 1

worker-0 永远在 CPU0,worker-1 永远在 CPU1,主线程可能在不同 CPU 间调度。

方式 B:后台运行 + 系统命令验证

把程序放到后台并抓取 PID:

~ » ./pin &               # 后台运行    
pid=$! # 保存进程 PID
echo "pid=$pid"

[1] 896965
pid=896965
main thread running on CPU worker-0 (TID=3896971
) bound to CPU0
worker-0 (TID=896971) running on CPU 0
worker-1 (TID=896972) bound to CPU1
worker-1 (TID=896972) running on CPU 1
---------------------------------------------------------------------------------------------------
~ » worker-0 (TID=worker-1896971) running on CPU 0 (TID=896972) running on CPU 1
main thread running on CPU 3
worker-0 (TID=896971) running on CPU 0
worker-1 (TID=896972) running on CPU 1
worker-0 (TID=896971) running on CPU 0
worker-1 (TID=896972) running on CPU 1
main thread running on CPU 5
worker-0 (TID=896971) running on CPU 0
worker-1 (TID=896972) running on CPU 1
main thread running on CPU 5
worker-0 (TID=896971) running on CPU 0
worker-1 (TID=896972) running on CPU 1
worker-0 (TID=896971) running on CPU 0
worker-1 (TID=896972) running on CPU 1
main thread running on CPU 5
worker-0 (TID=896971) running on CPU 0
worker-1 (TID=896972) running on CPU 1
worker-0 (TID=896971) running on CPU 0
worker-1 (TID=896972) running on CPU 1
worker-0 (TID=896971) running on CPU 0
worker-1 (TID=896972) running on CPU 1

[1] + 896965 done ./pin
-------------------------------

查看该进程的线程级 CPU 运行核(psr 列):

ps -o pid,tid,psr,comm -Lp "$pid"
  • pid:进程号

  • tid:线程 TID

  • psr:该线程当前运行在哪个 CPU(随时刻变化;被绑核的线程只会出现在允许范围内)

示意输出:

  PID   TID PSR COMMAND
12344 12344 2 pin
12344 12345 0 pin # worker-0
12344 12346 1 pin # worker-1

用 taskset 查看每个线程的 允许 CPU 列表(亲和性设置):

# 查看整个进程(所有线程)允许的 CPU 集合
taskset -pc "$pid"

# 分别查看具体线程(用 TID)
taskset -pc 12345
taskset -pc 12346

被成功绑核的线程会显示类似:

pid 12345's current affinity list: 0
pid 12346's current affinity list: 1

用 /proc 进一步核验(最权威的来源之一):

# 线程 12345 的亲和性(以列表形式显示)
cat /proc/$pid/task/12345/status | grep -E 'Cpus_allowed_list|Cpus_allowed'
  • Cpus_allowed_list:人类可读的核列表,例如 0 或 0-1,4

  • Cpus_allowed:位掩码形式(16 进制)

最后,回收后台进程:

kill "$pid"
wait "$pid" 2>/dev/null

用命令行直接绑进程/线程

除了在代码里调用 sched_setaffinity,你也可以用 taskset 在进程级别直接限制亲和性:

# 启动时指定(只允许运行在 CPU0)
taskset -c 0 ./pin

# 已经在跑了,再修改进程亲和性(允许运行在 CPU0-3)
taskset -pc 0-3 <pid>

注意:taskset 设置的是进程级亲和性,对现有和新建的线程都生效; 而代码里对某个线程 sched_setaffinity 是线程级,粒度更细。

常见问题与补充

1) 机器核数不够:

先用 lscpu 确认逻辑 CPU 数(CPU(s)),避免对不存在的 cpu_id 调用 CPU_SET。

2) 多 NUMA/大规模 CPU 场景:

对超多核系统(>1024 核)可使用动态大小的位图族 API:CPU_ALLOC/CPU_FREE/CPU_ALLOC_SIZE/CPU_ZERO_S/CPU_SET_S,避免 cpu_set_t 固定大小不够。

3) 容器/调度器限制:

Docker/容器可能限制可见 CPU(例如 —cpuset-cpus),此时只能在被允许的核内绑定。

4) 权限:

一般无需 root 权限。但若你的环境对 CPU 亲和性设置做了额外限制,可能需要更高权限或调整安全策略。

5) 验证稳定性:

psr 是“当前时刻”的运行核,瞬时值会跳变;是否“被限制到某些核”应以 taskset -pc 和 /proc/…/status 的 Cpus_allowed_list 为准。

CMakeLists Eigen FCPX GNU Gazebo Git Interest KDL Life Linux Matrix ODE QoS ROS Ros UML Ubuntu VcXsrv algorithm algorithms axis-angle bode calibration chrome control cpp dB data_struct dots figure gdb git latex launch life linux mac math matlab memory motor moveit operator optimal algorithm python robot robotics ros ros2 rtb simulation stl thread tools twist urdf velocity vim web work wsl
知识共享许可协议