linux下浅谈线程绑定cpu

来源:互联网 发布:阿里云备案核验点 编辑:程序博客网 时间:2024/04/29 12:02

本博客处女秀,欢迎读者提出问题或拍砖。

1.线程绑定的关键API:

关于linux下线程绑定的api在网上资料很多,关键是用到两个系统API:

int pthread_setaffinity_np(pthread_tthread, size_t cpusetsize,const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_tthread, size_t cpusetsize, cpu_set_t *cpuset);
   从函数名以及参数名都很明了,唯一需要点解释下的可能就是cpu_set_t这个结构体。这个结构体的理解类似于select中的fd_set,可以理解为cpu集,也是通过约定好的宏来进行清除、设置以及判断:
//初始化,设为空
      void CPU_ZERO (cpu_set_t *set); 
      //将某个cpu加入cpu集中 
       void CPU_SET (int cpu, cpu_set_t *set); 
       //将某个cpu从cpu集中移出 
       void CPU_CLR (int cpu, cpu_set_t *set); 
       //判断某个cpu是否已在cpu集中设置了 
       int CPU_ISSET (int cpu, const cpu_set_t *set); 

       cpu集可以认为是一个掩码,每个设置的位都对应一个可以合法调度的 cpu,而未设置的位对应一个不可调度的 CPU。

注意这个cpu集是一个集合,最终系统决定选定线程在哪个cpu上执行,选定标准我暂时没了解。最终选定一个cpu上执行

而不是在多个cpu上执行。


2.线程绑定的目的:

线程绑定的主要目的是提高线程访问cpu的cache(缓存)命中率,从而提高程序的并行性能。线程绑定的并行优化程度和服务器架构有密切关系。

传统的多核运算是使用SMP(Symmetric Multi-Processor )模式:将多个处理器与一个集中的存储器和I/O总线相连。所有处理器只能访问同一个物理存储器,因此SMP系统有时也被称为一致存储器访问(UMA)结构体系,一致性意指无论在什么时候,处理器只能为内存的每个数据保持或共享唯一一个数值。很显然,SMP的缺点是可伸缩性有限,因为在存储器和I/O接口达到饱和的时候,增加处理器并不能获得更高的性能。
NUMA模式是一种分布式存储器访问方式,处理器可以同时访问不同的存储器地址,大幅度提高并行性。 NUMA模式下,处理器被划分成多个"节点"(node), 每个节点被分配有的本地存储器空间。 所有节点中的处理器都可以访问全部的系统物理存储器,但是访问本节点内的存储器所需要的时间,比访问某些远程节点内的存储器所花的时间要少得多。因此NUMA架构相比SMP架构上使用线程绑定的方式更能提高并行效率。

3.linux下线程绑定小例子:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <pthread.h>#include <sched.h>#include <sched.h>#define MAX_PROCESSNUM 256pthread_mutex_t mutex;int nProcessNum = 0;int GetProcessNum(){return sysconf(_SC_NPROCESSORS_CONF);}void *myfunc(void *arg){int bindcpunum = *(int *)arg;cpu_set_t mask;cpu_set_t get;int j = 0;char buf[256];CPU_ZERO(&mask);CPU_SET(bindcpunum,&mask);if (pthread_setaffinity_np(pthread_self(),sizeof(mask),&mask) < 0)fprintf(stderr,"set thread affinity failed\n");CPU_ZERO(&get);if (pthread_getaffinity_np(pthread_self(),sizeof(get),&get) < 0 )fprintf(stderr,"get thread affinity failed\n");for (j = 0;j < nProcessNum;j++){if (CPU_ISSET(j,&get))printf("thread %d is running in processor%d\n",pthread_self(),j);}pthread_mutex_lock(&mutex);printf("thread %d is working ...\n",bindcpunum);pthread_mutex_unlock(&mutex);for (j = 0; j < 100000;j++)memset(buf,0,sizeof(buf));pthread_mutex_lock(&mutex);printf("thread %d is done\n",bindcpunum);pthread_mutex_unlock(&mutex);pthread_exit(NULL);}int main(int argc,char *argv[]){int cpubindset[MAX_PROCESSNUM] = {0};pthread_mutex_init(&mutex,NULL);pthread_t tid[MAX_PROCESSNUM];nProcessNum = GetProcessNum();for (int i = 0; i < nProcessNum; i++){cpubindset[i] = i;if (pthread_create(&tid[i],NULL,&myfunc,(void *)&cpubindset[i]) != 0){fprintf(stderr,"thread create failed\n");return -1;}}for (int i = 0 ;i < nProcessNum; i++)pthread_join(tid[i],NULL);return 0;}

实例分析:

例子用系统API获取当前系统的处理器核数,根据这个核数创建对应的n个线程,并把这n个线程绑定到对应的cpu上,
并循环100000次执行一个费时操作memset(buf,0,sizeof(buf));
例子中使用互斥量保证线程同步,我测试时把费时操作外面又加了一层循环,然后执行测试程序,用top发现程序cpu
所占比例为1600%(我的机器有16个cpu核),证明每个cpu都在跑那个费时操作。

4.疑问:

1.使用线程绑定机制和不使用线程绑定到底区别在哪?

我个人觉得在使用线程绑定机制时,当多个线程都需要访问相同的数据,可以把这些线程都绑定到一个cpu上,提高cache

的命中率。

不使用线程绑定时线程分配到哪个cpu是由操作系统负责的,操作系统可能根据当前的cpu状态来进行调度,把空闲的cpu分给新创建的

线程。

欢迎大家补充或指出本文不足的地方。





原创粉丝点击