0. 前言
线程CPU 亲和性分两种:软亲和性和硬亲和性。
软亲和性主要由操作系统来实现,Linux 操作系统的调度器会倾向于保持一个进程不会频繁在多个 CPU 之间迁移,通常情况下调度器都会根据各个 CPU 的负载情况合理地调度运行中的进程,以减轻繁忙 CPU 的压力,提高所有进程整体性能。
硬亲和性由用户调用系统 API 实现自定义进程运行在指定的 CPU 上,从而满足特定进程的特殊性能需求。这些个 API 分别是:
- pthread_attr_setaffinity_np()
- pthread_setaffinity_np()
- sched_setaffinity()
1. pthread_setaffinity_np()
#include <pthread.h>
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize,
cpu_set_t *cpuset);
参数:
- thread:要设置 CPU 亲和性的线程;
- cpusetsize:cpuset 的size,一般使用 sizeof(cput_set_t);
- cpuset:指向 CPU 亲和性的集合,其中包含要绑定线程的 CPU;
返回值:
- 成功返回0,失败返回错误码;
依赖:
- libpthread,-lpthread
1.1 实例
#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int s;
cpu_set_t cpuset;
pthread_t thread;
thread = pthread_self();
/* Set affinity mask to include CPUs 0 to 7. */
CPU_ZERO(&cpuset); //清除cpuset,不包含任何CPU
for (size_t j = 0; j < 8; j++)
CPU_SET(j, &cpuset); //配置cpuset,这里cpu0 ~ cpu7
s = pthread_setaffinity_np(thread, sizeof(cpuset), &cpuset);
if (s != 0)
errc(EXIT_FAILURE, s, "pthread_setaffinity_np");
/* Check the actual affinity mask assigned to the thread. */
s = pthread_getaffinity_np(thread, sizeof(cpuset), &cpuset);
if (s != 0)
errc(EXIT_FAILURE, s, "pthread_getaffinity_np");
printf("Set returned by pthread_getaffinity_np() contained:\n");
for (size_t j = 0; j < CPU_SETSIZE; j++)
if (CPU_ISSET(j, &cpuset)) //测试该cpu是否在cpuset 中
printf(" CPU %zu\n", j);
exit(EXIT_SUCCESS);
}
注意注释中的几个函数:
- CPU_ZERO(&cpuset): 清除cpuset
- CPU_SET(cpu, &cpuset): 配置cpuset
- CPU_ISSET(cpu, &cpuset):测试cpuset中是否包含cpu
2. sched_setaffinity()
#define _GNU_SOURCE
#include <sched.h>
int sched_setaffinity(pid_t pid, size_t cpusetsize,
const cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize,
cpu_set_t *mask);
参数:
- pid:线程pid,如果0则表示当前调用的线程;
- cpusetsize:mask 的size,一般使用 sizeof(cput_set_t);
- mask:指向 CPU 亲和性的集合,其中包含要绑定线程的 CPU;
返回值:
- 成功返回0,失败返回错误码;
依赖:
- libc,-lc
2.1 实例
#define _GNU_SOURCE
#include <err.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int parentCPU, childCPU;
cpu_set_t set;
unsigned int nloops;
if (argc != 4) {
fprintf(stderr, "Usage: %s parent-cpu child-cpu num-loops\n",
argv[0]);
exit(EXIT_FAILURE);
}
parentCPU = atoi(argv[1]);
childCPU = atoi(argv[2]);
nloops = atoi(argv[3]);
CPU_ZERO(&set);
switch (fork()) {
case -1: /* Error */
err(EXIT_FAILURE, "fork");
case 0: /* Child */
CPU_SET(childCPU, &set);
if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
err(EXIT_FAILURE, "sched_setaffinity");
for (unsigned int j = 0; j < nloops; j++)
getppid();
exit(EXIT_SUCCESS);
default: /* Parent */
CPU_SET(parentCPU, &set);
if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)
err(EXIT_FAILURE, "sched_setaffinity");
for (unsigned int j = 0; j < nloops; j++)
getppid();
wait(NULL); /* Wait for child to terminate */
exit(EXIT_SUCCESS);
}
}
3. pthread_attr_setaffinity_np()
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <pthread.h>
int pthread_attr_setaffinity_np(pthread_attr_t *attr,
size_t cpusetsize, const cpu_set_t *cpuset);
int pthread_attr_getaffinity_np(const pthread_attr_t *attr,
size_t cpusetsize, cpu_set_t *cpuset);
参数:
- attr:线程属性;
- cpusetsize:cpuset 的size,一般使用 sizeof(cput_set_t);
- cpuset:指向 CPU 亲和性的集合,其中包含要绑定线程的 CPU;
返回值:
- 成功返回0,失败返回错误码;
依赖:
- libpthread,-lpthread
3.1 实例
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sched.h>
void *thread_func(void *arg)
{
usleep(10000);
// 获取当前线程的 CPU 亲和性
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
// 打印当前线程绑定的 CPU
for (int i = 0; i < CPU_SETSIZE; i++) {
if (CPU_ISSET(i, &cpuset)) {
printf("thread is running on cpu %d\n", i);
}
}
return NULL;
}
int main() {
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
// 创建线程并设置其只能在 cpu1 上运行
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(1, &mask);
pthread_attr_setaffinity_np(&attr, sizeof(mask), &mask);
pthread_create(&tid, &attr, thread_func, NULL);
pthread_attr_destroy(&attr);
pthread_join(tid, NULL);
return 0;
}
4. pthread_attr_setaffinity_np VS pthread_setaffinity_np
相同点:
- cpuset 在 CPU_ZERO() 清除后,都可以通过 CPU_SET() 配置一个或多个cpu到 cpuset中;
不同点:
- pthread_setaffinity_np() 函数主要是设置 thread 的CPU 集,这个集由 cpuset指定;
- pthread_attr_setaffinity_np() 函数将 attr 引用的线程属性对象的 CPU mask 指向 cpuset 指定的值;
pthread_setaffinity_np() 函数参数依赖 thread,所以在调用该函数的时候,thread 是已经存在的。
pthread_attr_setaffinity_np() 函数参数依赖 attr而不是 thread,即可以先调用该函数,然后再创建将 attr 带入 pthread_create() 创建线程。
参考:
pthread_setaffinity_np(3) - Linux manual page