1. 绑定目的
在 Linux 系统中,线程调度是由内核自主完成的。当系统运行在多核 CPU 上时,线程有可能在不同的 CPU 核上来回切换执行。这不利于 CPU 的缓存,缺页率较高。
以 Intel i5 CPU 为例。在多核 CPU 结构中,每个 CPU 有各自的 L1、L2 缓存,而 L3 缓存是共用的。如果一个线程在 CPU 间来回切换,各个 CPU 的缓存的命中率将会降低。而如果线程不管如何调度,都始终可以在一个 CPU 上执行,那么其数据的 L1、L2 缓存的命中率将得到显著提高。
2. 使用命令进行绑定
使用命令 taskset 可以把线程绑定为指定 CPU 上。
taskset 命令使用方法如下:
taskset -p <cpu_set> <pid>
线程的 pid 可以使用 pstree -p | grep <pthread_name>
来获取。但该方法的前提是线程必须已设置名称,否则无法使用 pstree 来查看线程。
cpu_set 为线程对应的 CPU 集,为整型类型,其数值为 1 的 bit 位对应着哪一号 CPU(从 0 起算)。例如 cpu_set 为 1(0001)时对应 1 号 CPU,为 4(0100)时对应 2 号 CPU。
当 cpu_set 有多个 bit 位为 1 时,表示系统会把线程随机调度到在这些 CPU 之一上运行。例如当线程对应的 cpu_set 为 3 即二进制 0011 时,表示该线程将被系统随机地在 0 号 CPU 或 1 号 CPU 上调度运行。
当 taskset 不使用 <cpu_set>
只使用 -p <pid>
时,表示查询该线程对应的 CPU 集情况。
以下示例将使用命令 taskset 把线程号为 1958 的线程绑定为 1 号 CPU 上:
~ # taskset -p 1958
pid 1958's current affinity mask: 3
~ # taskset -p 2 1958
pid 1958's current affinity mask: 3
pid 1958's new affinity mask: 2
3. 在程序中进行绑定
在 Linux 中,可以使用 sched_setaffinity()
改变线程对应的 CPU 集,把线程绑定到指定 CPU 上。
函数 sched_setaffinity 所属头文件 <sched.h>
,函数原型如下:
int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t* mask);
可见,该实际上就是把线程 pid
设置为指定 CPU 集。当传参 pid
为 0 时表示把当前线程的 CPU 集设置为 mask
。
cpu_set_t
类型本质上为一个整型数组,Linux 提供以下 5 个宏函数操作 CPU 集(以下数据类型仅供阅读):
宏函数 | 功能 |
---|---|
CPU_SET(int cpu, cpu_set_t* cpusetp) |
把 cpu 添加到 cpusetp 中 |
CPU_CLR(int cpu, cpu_set_t* cpusetp) |
把 cpu 从 cpusetp 中删除 |
int CPU_ISSET(int cpu, cpu_set_t* cpusetp) |
判断 cpu 是否在 cpusetp 中,存在则返回 1,否则返回 0 |
CPU_ZERO(cpu_set_t* cpusetp) |
清空 cpusetp ,即把 cpusetp 的每一 bit 位清零 |
CPU_COUNT(cpu_set_t* cpusetp) |
查询 cpusetp 的 bit 位为 1 的个数,即线程调度的 CPU 个数 |
其中,cpu
指的是哪一号 CPU,该编号从 0 起算。
在使用以上宏时,需先打开宏 __USE_CHU
与 _GNU_SOURCE
告诉编译器启动以上宏函数,且这些宏声明必须在所有头文件声明之前:
#ifndef __USE_CHU
#define __USE_CHU
#endif
#define _GNU_SOURCE
#include <sched.h>
以下示例把当前线程绑定为 3 号 CPU 上:
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(3, &mask);
sched_setaffinity(0, sizeof(cpu_set_t), &mask);