个人技术分享

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;

返回值:

依赖:

  • 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;

返回值:

依赖:

  • 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;

返回值:

依赖:

  • 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

sched_setaffinity(2) - Linux manual page

 pthread_attr_setaffinity_np(3) - Linux manual page