说到多进程和多线程,我就想起了《The art of Multiprocessor Programming》中的第一章的例子(我就看了第一章)

 

On the first day of your new job, your boss asks you to find all primes between 1 and 10^10(never mind why), using a parallel machine that supports ten concurrent threads. This machine is rented by the minute, so the longer your program takes, the more it costs. You want to make a good impression. What do you do?

我对多线程编程不是很熟,所以想用这个例子学习一下多线程编程。

单线程编程

如果不会用线程,那么就用最简单的单线程解决这个问题。

  1. //example1.c
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. #include <math.h> 
  5. long long isPrime(int num) 
  6.     long long i, s; 
  7.     s = (long long)sqrt(double(num)); 
  8.     for(i = 2; i < s; i++) 
  9.         if(num % i == 0) 
  10.             return 0; 
  11.     return 1; 
  12. int main() 
  13.     long long i,primes_count = 0; 
  14.     for(i = 1 ; i <= 10000000000L; i++) 
  15.         primes_count +=  isPrime(i); 
  16.     printf("%lld primes); 
  17.     return 0; 

注意:编译时加上-lm,gcc -lm example1.c  ; 打印long long整数要用%lld

如果真用这段代码跑10000000000L,估计几个小时跑不完,几天不知道能跑完吗。我打算在16核的机器上跑一下,不过16核和单核一样,因为是单线程。

 

多线程方法1

如果会使用线程,一个简单的方法是10个线程平分这些数。

-------------------------------------------------------------------------------------------

pthread相关数据结构和函数

pthread_t 调用pthread_t返回的线程号,就是一个unsigned int; 

int pthread_create(pthread_t *, pthread_attr_t *,  void*  (*) (void*) , void*);

第一个参数是一个pthread_t,第二个是pthread_attr_t,具体看下面的用法,第三个是线程要运行的主函数,第四个是要给这个函数的参数。

(void*) (*) (void*) 是一个函数类型,就是类似于 void *  run (void*)的函数。

void pthread_exit() 线程退出

int pthread_join(pthread_t *, void **) 主线程等待子线程退出,第二个参数直接用NULL就行

----------------------------------------------------------------------------------------------------------

  1. //example2.c
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. #include <pthread.h> 
  5. #include <sys/types.h> 
  6. #include <unistd.h> 
  7. #include <math.h> 
  8. long long isPrime(long long num) 
  9.     long long i, s; 
  10.     s = (long long)sqrt((double)num); 
  11.     for(i = 2; i < s; i++) 
  12.         if(num % i == 0) 
  13.             return 0; 
  14.     return 1; 
  15.  
  16. void * run(void * param) 
  17.     long long start,end; 
  18.     long long i,primes_count = 0; 
  19.     start =( (long long*)param)[0]; 
  20.     end  =( (long long*)param)[1]; 
  21.     for(i = start ; i < end; i++) 
  22.         primes_count +=  isPrime(i); 
  23.     printf("Process %d Thread %u gets %lld primes from %lld to %lld\n",getpid(),(unsigned)pthread_self(),primes_count,start,end); 
  24.     pthread_exit(0); 
  25.  
  26. int main() 
  27.     pthread_t tid[10]; 
  28.     pthread_attr_t attr; 
  29.     long long start_end[10][2]; 
  30.     long long  i; 
  31.     for(i = 0; i < 10; i++) 
  32.     { 
  33.         start_end[i][0] = i*10000; 
  34.         start_end[i][1] = (i+1)*10000;  
  35.         pthread_attr_init(&attr); 
  36.         pthread_create(&tid[i],&attr,run,(void*)start_end[i]); 
  37.     } 
  38.     for(i= 0; i < 10; i++) 
  39.         pthread_join(tid[i],NULL); 
  40.     return 0; 

注意:使用pthread编译要加上-lpthread。 gcc -lm -lpthread example2.c

运行结果:(不是10^10,只计算到100000)

多进程与多线程(1)_多进程

------------------------------------------------------------------------------------------------

遇到的问题:

最开始我的主函数写成这样:

  1. int main() 
  2.     pthread_t tid[10]; 
  3.     pthread_attr_t attr; 
  4.     long long start_end[2]; 
  5.     long long i; 
  6.     for(i = 0; i < 10; i++) 
  7.     { 
  8.         start_end[0] = i*10000; 
  9.         start_end[1] = (i+1)*10000; 
  10.         pthread_attr_init(&attr); 
  11.         pthread_create(&tid[i],&attr,run,(void*)start_end); 
  12.         pthread_join(tid[i],NULL); 
  13.     } 
  14.     return 0; 

很明显pthread_join地方写错了,这样第一个进程运行完了才会创建第二个,但是由于每个线程平分,所以我没发现结果有异常。

写第三个程序时pthread_join也写错了位置,这才发现。但是我将pthread_join放到后面时,发现每个线程处理的数据段都是90000-100000,没找到原因,因此我的start_end改成了二维数组,这样各个线程就不会干扰了,具体的原因以后再研究。

--------------------------------------------------------------------------------------

这个程序应该比第一个快,但是判断第一段数据的线程使用的时间肯定没有最后一段使用的时间的千分之一,最后9个人看一个人干活,我突然想到了什么。。。

多线程方法2

多线程的目的就是压榨计算资源。我们可以使用下面的方法。

每个线程取一个数,然后判断,判断完了继续取下一个,让每个线程都不停的干活

线程取了1,判断,线程2取2判断,线程3取3,这时线程1干完了,接着取4,线程2又取5。。。。

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <pthread.h> 
  4. #include <sys/types.h> 
  5. #include <unistd.h> 
  6. #include <math.h> 
  7. long long number = 1; //每个线程先取数,再把这个数加1
  8. long long isPrime(long long num) 
  9.     long long i, s; 
  10.     s = (long long)sqrt((double)num); 
  11.     for(i = 2; i < s; i++) 
  12.         if(num % i == 0) 
  13.             return 0; 
  14.     return 1; 
  15. static pthread_mutex_t mutex; 
  16. void * run(void * param) 
  17.     long long primes_count=0; 
  18.     long long i = 1; 
  19.     while(i < 100000) 
  20.     { 
  21. //要使用锁
  22.         pthread_mutex_lock(&mutex); 
  23.         i = number; 
  24.         number ++; 
  25.         pthread_mutex_unlock(&mutex); 
  26.         primes_count +=  isPrime(i); 
  27.     } 
  28.     printf("Thread %u gets %lld primes\n",(unsigned  int)pthread_self(),primes_count); 
  29.     pthread_exit(0); 
  30.  
  31.  int main() 
  32.     pthread_t tid[10]; 
  33.     pthread_attr_t attr; 
  34.     pthread_mutex_init(&mutex,NULL); 
  35.     int i; 
  36.  
  37.     for(i = 0; i < 10; i++) 
  38.     { 
  39.         pthread_attr_init(&attr); 
  40.         pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); 
  41.         pthread_create(&tid[i],&attr,run,NULL); 
  42.         //pthread_join(tid[i],NULL); 一开始把join放这,结果只有第一个线程干活,找了好久才发现位置错了
  43.     } 
  44.     for(i = 0; i < 10; i++) 
  45.         pthread_join(tid[i],NULL); 
  46.     return 0; 

运行结果:

多进程与多线程(1)_shmget_02

多进程实现

用多线程能完成的,多进程也可以。

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <pthread.h> 
  4. #include <sys/types.h> 
  5. #include <unistd.h> 
  6. #include <math.h> 
  7. #include <sys/ipc.h> 
  8. #include <sys/shm.h> 
  9. #include <errno.h> 
  10. #include <string.h> 
  11. long long isPrime(long long num) 
  12.     long long i, s; 
  13.     s = (long long)sqrt((double)num); 
  14.     for(i = 2; i < s; i++) 
  15.         if(num % i == 0) 
  16.             return 0; 
  17.     return 1; 
  18.  
  19. int main() 
  20.     pid_t pid[10]; 
  21.     int shmid; 
  22.     long long  i,primes_count=0; 
  23.     long long * addr; 
  24.     shmid = shmget(IPC_PRIVATE,4096,IPC_CREAT|0600); 
  25.     if(shmid < 0) 
  26.     { 
  27.         printf("get shared memory failed\n"); 
  28.         exit(1); 
  29.     } 
  30.     addr = (long long*) shmat(shmid,0,0); 
  31.     *addr = 1; 
  32.     for(i = 0; i < 3; i++) 
  33.         fork(); 
  34.     while(i < 100000) 
  35.     { 
  36.         i = (*addr); 
  37.         (*addr) ++; 
  38.         primes_count += isPrime(i); 
  39.     } 
  40.     printf("pid %d get %lld primes\n",getpid(),primes_count); 
  41.     return 0; 

 

注意:(1)使用循环fork,那么子进程还会fork,因此fork三次就得到了8个进程,在fork就16个进程了。要想产生10个进程,可以在主进程里写10个fork语句,还得写很多if(fork()==0)语句,懒得写,就直接用循环了。加上主进程一个8个,先生完了孩子再干活,生完之后都成了兄弟,一起干。

           (2)这个程序有个严重的问题,使用了共享内存,但是没有使用同步机制,没有锁,挺麻烦的,不会用,就没写。结果可能不正确

  运行结果:

多进程与多线程(1)_shmget_03

 小结

我就截几个图

多进程与多线程(1)_多进程_04

这是单线程的,CPU使用了100%,看来也不错,但是我的CPU有200%,有两个核(我用的虚拟机,可以分16个核哦亲),因此单线程不能充分利用计算资源。

多进程与多线程(1)_shmget_05

多线程的把我的CPU都用完了。哈哈

多进程与多线程(1)_shmget_06

多进程的呢?算一算,也是200%。

其实在Linux里面线程也是进程,称为LWP(Light Weighted Process),只不过在生成的时候参数不同。在以后的文章里会深入研究。