您的位置:首页 > 运维架构 > Linux

linux下浅谈线程绑定cpu

2014-10-16 19:04 381 查看

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 256
pthread_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分给新创建的

线程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: