前两天看了些并行计算的文章,了解了一些并行计算的方法和原理。然后发现多线程实现里面还有个openMP,这个以前从来没见过(火星了),之前只是知道pthread线程库和微软也实现了一套线程。又看了看openMP的一些教程才知道它是怎么回事。
pthread全称应该是POSIX THREAD,顾名思义这个肯定是按照POSIX对线程的标准而设计的。目前我所知道的有两个版本:Linux Thread(较早)和NPTL(主流?)。pthread库是一套关于线程的API,提供“遵循”(各平台实现各异)POSIX标准的线程相关的功能。
openMP不同于pthread的地方是,它是根植于编译器的(也要包含头文件omp.h),而不是在各系统平台是做文章。它貌似更偏向于将原来串行化的程序,通过加入一些适当的编译器指令(compiler directive)变成并行执行,从而提高代码运行的速率。如:
1 #include<omp.h> 2 #include<stdio.h> 3 4 #define ARRAY_SIZE 1000 5 #define CHUNK_SIZE 100 6 7 int main() 8 { 9 int array[ARRAY_SIZE]; 10 int thread_num = ARRAY_SIZE/CHUNK_SIZE+1; 11 omp_set_num_threads(thread_num); 12 13 //init array 14 int i; 15 for(i=0;i<ARRAY_SIZE;i++) 16 { 17 array[i]=i; 18 } 19 20 #pragma omp parallel for schedule(guided,CHUNK_SIZE) private(i) 21 for(i=0;i<ARRAY_SIZE;i++) 22 { 23 int n = array[i]; 24 int num_of_one=0; 25 if(n!=0) 26 { 27 num_of_one++; 28 while((n=n&(n-1))!=0) 29 { 30 num_of_one++; 31 } 32 } 33 array[i]=num_of_one; 34 35 } 36 for(i=0;i<ARRAY_SIZE;i++) 37 { 38 printf("%d ",array[i]); 39 } 40 printf("\n"); 41 return 0; 42 43 } 44 45
上面一段代码是通过加了一条函数调用(11行)和一条编译器指令(20行),从而将原来的循环分给多个线程来做。(本程序是计算0~ArraySize-1的每个数中二进制包含1个数)。
而对于一开始就打算用并行方法来实现的程序,用pthread应该是更方便和更清晰。
下面是分别用pthread和openMP实现的worker_and_consumer:
pthread版:
1 #include<unistd.h> 2 #include<pthread.h> 3 #include<stdio.h> 4 #include<stdlib.h> 5 6 #define SIZE 100 7 #define THREAD_NUM_WORKER 15 8 #define THREAD_NUM_CONSUMER 10 9 #define SLEEP_WORKERS 2 10 #define SLEEP_CONSUMERS 1 11 12 int warehouse[SIZE]; 13 int at =-1; 14 int is_end =0; 15 pthread_mutex_t space = PTHREAD_MUTEX_INITIALIZER; 16 pthread_mutex_t end = PTHREAD_MUTEX_INITIALIZER; 17 18 void* consumer_func(void*); 19 void* worker_func(void*); 20 21 int main() 22 { 23 pthread_t workers[THREAD_NUM_WORKER]; 24 pthread_t consumers[THREAD_NUM_CONSUMER]; 25 int i,j; 26 int n; 27 for(i=0;i<THREAD_NUM_WORKER;i++) 28 pthread_create(&workers[i],NULL,worker_func,NULL); 29 for(j=0;j<THREAD_NUM_CONSUMER;j++) 30 pthread_create(&consumers[j],NULL,consumer_func,NULL); 31 while(is_end==0) 32 { 33 scanf("%d",&n); 34 if(n==0) 35 { 36 pthread_mutex_lock(&end); 37 is_end=1; 38 pthread_mutex_unlock(&end); 39 } 40 } 41 for(i=0;i<THREAD_NUM_WORKER;i++) 42 pthread_join(workers[i],NULL); 43 for(j=0;j<THREAD_NUM_CONSUMER;j++) 44 pthread_join(consumers[j],NULL); 45 return 0; 46 } 47 48 void* worker_func(void* var) 49 { 50 while(1) 51 { 52 if(is_end) 53 break; 54 //保护at变量 55 pthread_mutex_lock(&space); 56 if(SIZE-at-1>0) 57 { 58 printf("Make %d by worker %lld ",warehouse[++at]=rand(),pthread_self()); 59 printf("and at is %d\n",at); 60 } 61 pthread_mutex_unlock(&space); 62 sleep(SLEEP_WORKERS); 63 } 64 return NULL; 65 } 66 67 68 void* consumer_func(void* var) 69 { 70 while(1) 71 { 72 if(is_end) 73 break; 74 pthread_mutex_lock(&space); 75 if(at>=0) 76 { 77 printf("Got %d by consumer %lld\n",warehouse[at--],pthread_self()); 78 printf("and at is %d\n",at); 79 } 80 pthread_mutex_unlock(&space); 81 sleep(SLEEP_CONSUMERS); 82 } 83 return NULL; 84 } 85 86 87 88
openMP版:
1 #include<unistd.h> 2 #include<stdio.h> 3 #include<stdlib.h> 4 #include<omp.h> 5 6 7 #define SIZE 100 8 #define THREAD_NUM_WORKER 15 9 #define THREAD_NUM_CONSUMER 10 10 #define SLEEP_WORKERS 2 11 #define SLEEP_CONSUMERS 1 12 13 int warehouse[SIZE]; 14 int at =-1; 15 int is_end =0; 16 17 void start_workers() 18 { 19 omp_set_num_threads(THREAD_NUM_WORKER); 20 #pragma omp parallel default(shared) 21 { 22 if(omp_get_thread_num()==0) 23 printf("worker num is %d\n",omp_get_num_threads()); 24 while(1) 25 { 26 if(is_end) 27 break; 28 //保护at变量 29 #pragma omp critical(space) 30 { 31 if(SIZE-at-1>0) 32 { 33 printf("Make %d by worker %d ",warehouse[++at]=rand(),omp_get_thread_num()); 34 printf("and at is %d\n",at); 35 } 36 } 37 sleep(SLEEP_WORKERS); 38 } 39 } 40 } 41 42 43 void start_consumers(void) 44 { 45 omp_set_num_threads(THREAD_NUM_CONSUMER); 46 #pragma omp parallel default(shared) 47 { 48 if(omp_get_thread_num()==0) 49 printf("consumer num is %d\n",omp_get_num_threads()); 50 while(1) 51 { 52 if(is_end) 53 break; 54 #pragma omp critical(space) 55 { 56 if(at>=0) 57 { 58 printf("Got %d by consumer %d\n",warehouse[at--],omp_get_thread_num()); 59 printf("and at is %d\n",at); 60 } 61 } 62 sleep(SLEEP_CONSUMERS); 63 } 64 } 65 } 66 67 int main() 68 { 69 omp_set_dynamic(0); 70 omp_set_nested(1);//这个不设置的话,就不能嵌套fork子线程咯 71 //先设置3个线程,每个线程完成一个section 72 omp_set_num_threads(3); 73 #pragma omp parallel sections 74 { 75 #pragma omp section 76 { 77 start_workers(); 78 } 79 #pragma omp section 80 { 81 start_consumers(); 82 } 83 #pragma omp section 84 { 85 int in; 86 scanf("%d",&in); 87 if(!in) 88 { 89 //保护is_end 90 #pragma omg critical(end) 91 is_end =1; 92 } 93 } 94 } 95 return 0; 96 } 97 98 99
最后说一下,用openMP,编译时要加上选项-fopenmp,编译pthread时加上链接-lpthread。另外openMP有个缺点是若是代码中编译指令出错时,找错还是挺麻烦的,就像昨晚我把#pragma omp parallel写成了#pragma omg parallel,结果编译链接通过后却始终只有一个线程(主线程),找了好久...囧!