linux下的单进程多线程的程序,要实现每个线程平均分配到多核cpu,主要有2个方法
1:利用linux系统自己的线程切换机制,linux有一个服务叫做irqbalance,这个服务是linux系统自带的,默认会启动,这个服务的作用就是把多线程平均分配到CPU的每个核上面,只要这个服务不停止,多线程分配就可以自己实现。但是要注意,如果线程函数内部的有某个循环,且该循环内没有任何系统调用的话,可能会导致这个线程的CPU时间无法被切换出去。也就是占满CPU现象,此时加个系统调用,例如sleep,线程所占的CPU时间就可以切换出去了。
2:利用pthread库自带的线程亲和性设置函数,来设置线程在某个CPU核心上跑,这个需要在程序内部实现。同时注意不要和进程亲和性设置搞混淆了
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);
从函数名以及参数名都很明了,唯一需要点解释下的可能就是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);
将线程绑定到不同的processor上:
这里加入有两个cpu,一个cpu五个核
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sched.h>
inline int set_cpu(int i)
{
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(i,&mask);
printf("thread %u, i = %d\n", pthread_self(), i);
if(-1 == pthread_setaffinity_np(pthread_self() ,sizeof(mask),&mask))
{
fprintf(stderr, "pthread_setaffinity_np erro\n");
return -1;
}
return 0;
}
void *thread_ip_bitmap_set(void *p)
{
uint32_t i, ip;
struct id_ipmap *entry;
thread_info_t *info = (thread_info_t *)p;
if(set_cpu(info->id))
{
return NULL;
}
printf("info->id = %d; info->start = %d; info->end = %d, sub = %d\n", info->id, info->start, info->end, info->start - info->end);
for (i = info->start; i < info->end; ++i) {
entry = &ipmap_queue[i];
for (ip = entry->ip_start; ip < entry->ip_end; ip++) {
ip_bitmap_set(adns_htobe32(ip), entry->id);
}
}
printf("info->id = %d finished\n", info->id);
return NULL;
}
int main()
{
for(thread_index=0; thread_index < 10; thread_index++)
pthread_create(&thread_id[thread_index],NULL, thread_ip_bitmap_set, &thread_info[thread_index]);
}
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <sys/types.h>
#include<string.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
inline int set_cpu(int i)
{
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(i,&mask);
printf("thread %u, i = %d\n", pthread_self(), i);
if(-1 == pthread_setaffinity_np(pthread_self() ,sizeof(mask),&mask))
{
return -1;
}
return 0;
}
void *fun(void *i)
{
if(set_cpu(*(int *)i))
{
printf("set cpu erro\n");
}
long long a = 0;
while(1)
{
a += rand();
}
return NULL;
}
int main (int argc, const char * argv[]) {
int i;
int cpu_nums = sysconf(_SC_NPROCESSORS_CONF);
printf("cpu_numbs = %d\n", cpu_nums);
pthread_t Thread[10];
int tmp[10];
for(i = 0; i < 10; ++i)
{
tmp[i] = i;
pthread_create(&Thread[i],NULL,fun, &tmp[i]);
}
for(i = 0; i < 10; ++i)
{
pthread_join(Thread[i],NULL);
}
return 0;
}
coolshell最新的文章《性能调优攻略》在“多核CPU调优”章节,提到“我们不能任由操作系统负载均衡,因为我们自己更了解自己的程序,所以,我们可以手动地为其分配CPU核,而不会过多地占用CPU0,或是让我们关键进程和一堆别的进程挤在一起。”。在文章中提到了Linux下的一个工具,taskset,可以设定单个进程运行的CPU。
同时,因为最近在看redis的相关资料,redis作为单进程模型的程序,为了充分利用多核CPU,常常在一台server上会启动多个实例。而为了减少切换的开销,有必要为每个实例指定其所运行的CPU。
下文,将会介绍taskset命令,以及sched_setaffinity系统调用,两者均可以指定进程运行的CPU实例。
1.taskset
taskset是LINUX提供的一个命令(ubuntu系统可能需要自行安装,schedutils package)。他可以让某个程序运行在某个(或)某些CPU上。
以下均以redis-server举例。
1)显示进程运行的CPU
命令taskset -p 21184
显示结果:
pid 21184's current affinity mask: ffffff
注:21184是redis-server运行的pid
显示结果的ffffff实际上是二进制24个低位均为1的bitmask,每一个1对应于1个CPU,表示该进程在24个CPU上运行
2)指定进程运行在某个特定的CPU上
命令taskset -pc 3 21184
显示结果:
pid 21184's current affinity list: 0-23
pid 21184's new affinity list: 3
注:3表示CPU将只会运行在第4个CPU上(从0开始计数)。
3)进程启动时指定CPU
命令taskset -c 1 ./redis-server ../redis.conf
结合这上边三个例子,再看下taskset的manual,就比较清楚了。
OPTIONS
-p, --pid
operate on an existing PID and not launch a new task
-c, --cpu-list
specify a numerical list of processors instead of a bitmask. The list may contain multiple items, separated by comma, and ranges. For example, 0,5,7,9-11.
2.sched_setaffinity系统调用
问题描述
sched_setaffinity可以将某个进程绑定到一个特定的CPU。你比操作系统更了解自己的程序,为了避免调度器愚蠢的调度你的程序,或是为了在多线程程序中避免缓存失效造成的开销,你可能会希望这样做。如下是sched_setaffinity的例子,其函数手册可以参考(linuxmanpages.com - ):
/* Short test program to test sched_setaffinity
* (which sets the affinity of processes to processors).
* Compile: gcc sched_setaffinity_test.c
* -o sched_setaffinity_test -lm
* Usage: ./sched_setaffinity_test
*
* Open a "top"-window at the same time and see all the work
* being done on CPU 0 first and after a short wait on CPU 1.
* Repeat with different numbers to make sure, it is not a
* coincidence.
*/
#include <stdio.h>
#include <math.h>
#include <sched.h>
double waste_time(long n)
{
double res = 0;
long i = 0;
while(i <n * 200000) {
i++;
res += sqrt (i);
}
return res;
}
int main(int argc, char **argv)
{
unsigned long mask = 1; /* processor 0 */
/* bind process to processor 0 */
if (sched_setaffinity(0, sizeof(mask), &mask) <0) {
perror("sched_setaffinity");
}
/* waste some time so the work is visible with "top" */
printf ("result: %f\n", waste_time (2000));
mask = 2; /* process switches to processor 1 now */
if (sched_setaffinity(0, sizeof(mask), &mask) <0) {
perror("sched_setaffinity");
}
/* waste some more time to see the processor switch */
printf ("result: %f\n", waste_time (2000));
}
根据你CPU的快慢,调整waste_time的参数。然后使用top命令,就可以看到进程在不同CPU之间的切换。(启动top命令后按“1”,可以看到各个CPU的情况)。
父进程和子进程之间会继承对affinity的设置。因此,大胆猜测,taskset实际上是首先执行了sched_setaffinity系统调用,然后fork+exec用户指定的进程。