多线程与多进程

转载:http://www.cnblogs.com/eavn/archive/2010/08/28/1811381.html

在Unix上编程采用多线程还是多进程的争执由来已久,这种争执最常见到在C/S通讯中服务端并发技术 的选型上,比如WEB服务器技术中,Apache是采用多进程的(perfork模式,每客户连接对应一个进程,每进程中只存在唯一一个执行线程), Java的Web容器Tomcat、Websphere等都是多线程的(每客户连接对应一个线程,所有线程都在一个进程中)。

从Unix发展历史看,伴随着Unix的诞生进程就出现了,而线程很晚才被系统支持,例如Linux直到内核2.6,才支持符合Posix规范的NPTL线程库。进程和线程的特点,也就是各自的优缺点如下:

进程优点:编程、调试简单,可靠性较高。
进程缺点:创建、销毁、切换速度慢,内存、资源占用大。
线程优点:创建、销毁、切换速度快,内存、资源占用小。
线程缺点:编程、调试复杂,可靠性较差。

上面的对比可以归结为一句话:“线程快而进程可靠性高”。线程有个别名叫“轻量级进程”,在有的书籍资料上介绍线程可以十倍、百倍的效率快于进程; 而进程之间不共享数据,没有锁问题,结构简单,一个进程崩溃不像线程那样影响全局,因此比较可靠。我相信这个观点可以被大部分人所接受,因为和我们所接受 的知识概念是相符的。

在写这篇文章前,我也属于这“大部分人”,这两年在用C语言编写的几个C/S通讯程序中,因时间紧总是采用多进程并发技术,而且是比较简单的现场为 每客户fork()一个进程,当时总是担心并发量增大时负荷能否承受,盘算着等时间充裕了将它改为多线程形式,或者改为预先创建进程的形式,直到最近在网 上看到了一篇论文《Linux系统下多线程与多进程性能分析》作者“周丽 焦程波 兰巨龙”,才认真思考这个问题,我自己也做了实验,结论和论文作者的相似,但对大部分人可以说是颠覆性的。

下面是得出结论的实验步骤和过程,结论究竟是怎样的? 感兴趣就一起看看吧。

 

实验代码使用周丽论文中的代码样例,我做了少量修改,值得注意的是这样的区别:

论文实验和我的实验时间不同,论文所处的年代linux内核是2.4,我的实验linux内核是2.6,2.6使用的线程库是NPTL,2.4使用的是老的Linux线程库(用进程模拟线程的那个LinuxThread)。

论文实验和我用的机器不同,论文描述了使用的环境:单 cpu 机器基本配置为:celeron 2.0 GZ, 256M, Linux 9.2,内核 2.4.8。我的环境是我的工作本本:单cpu单核celeron(R) M 1.5 GZ,1.5G内存,ubuntu10.04 desktop,内核2.6.32。

进程实验代码(fork.c):

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <signal.h>
  4.  
  5. #define P_NUMBER 255    /* 并发进程数量 */
  6. #define COUNT 100       /* 每进程打印字符串次数 */
  7. #define TEST_LOGFILE "logFile.log"
  8. FILE *logFile = NULL;
  9.  
  10. char *s = "hello linux\0";
  11.  
  12. int main()
  13. {
  14.     int i = 0,j = 0;
  15.     logFile = fopen(TEST_LOGFILE, "a+")/* 打开日志文件 */
  16.     for(i = 0; i < P_NUMBER; i++)
  17.     {
  18.         if(fork() == 0) /* 创建子进程,if(fork() == 0){}这段代码是子进程运行区间 */
  19.         {
  20.             for(j = 0;j < COUNT; j++)
  21.             {
  22.                 printf("[%d]%s\n", j, s)/* 向控制台输出 */
  23.                 fprintf(logFile,"[%d]%s\n", j, s)/* 向日志文件输出 */
  24.             }
  25.             exit(0)/* 子进程结束 */
  26.         }
  27.     }
  28.  
  29.     for(i = 0; i < P_NUMBER; i++) /* 回收子进程 */
  30.     {
  31.         wait(0);
  32.     }
  33.  
  34.     printf("OK\n");
  35.     return 0;
  36. }

线程实验代码(thread.c):

  1. #include <pthread.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5.  
  6. #define P_NUMBER 255    /* 并发线程数量 */
  7. #define COUNT 100       /* 每线程打印字符串次数 */
  8. #define Test_Log "logFIle.log"
  9. FILE *logFile = NULL;
  10.  
  11. char *s = "hello linux\0";
  12.  
  13. print_hello_linux() /* 线程执行的函数 */
  14. {
  15.     int i = 0;
  16.     for(i = 0; i < COUNT; i++)
  17.     {
  18.         printf("[%d]%s\n", i, s)/* 向控制台输出 */
  19.         fprintf(logFile, "[%d]%s\n", i, s)/* 向日志文件输出 */
  20.     }
  21.     pthread_exit(0)/* 线程结束 */
  22. }
  23.  
  24. int main()
  25. {
  26.     int i = 0;
  27.     pthread_t pid[P_NUMBER]/* 线程数组 */
  28.     logFile = fopen(Test_Log, "a+")/* 打开日志文件 */
  29.  
  30.     for(i = 0; i < P_NUMBER; i++)
  31.         pthread_create(&pid[i]NULL(void *)print_hello_linux, NULL)/* 创建线程 */
  32.  
  33.     for(i = 0; i < P_NUMBER; i++)
  34.         pthread_join(pid[i],NULL)/* 回收线程 */
  35.  
  36.     printf("OK\n");
  37.     return 0;
  38. }

两段程序做的事情是一样的,都是创建“若干”个进程/线程,每个创建出的进程/线程打印“若干”条“hello linux”字符串到控制台和日志文件,两个“若干”由两个宏 P_NUMBER和COUNT分别定义,程序编译指令如下:

diaoyf@ali:~/tmp1$ gcc -o fork fork.c
diaoyf@ali:~/tmp1$ gcc -lpthread -o thread thread.c

实验通过time指令执行两个程序,抄录time输出的挂钟时间(real时间):

time ./fork
time ./thread

每批次的实验通过改动宏 P_NUMBER和COUNT来调整进程/线程数量和打印次数,每批次测试五轮,得到的结果如下:

一、重复周丽论文实验步骤


第1次 第2次 第3次 第4次 第5次 平均
多进程 0m1.277s 0m1.175s 0m1.227s 0m1.245s 0m1.228s 0m1.230s
多线程 0m1.150s 0m1.192s 0m1.095s 0m1.128s 0m1.177s 0m1.148s

进程线程数:255 / 打印次数:100


第1次 第2次 第3次 第4次 第5次 平均
多进程 0m6.341s 0m6.121s 0m5.966s 0m6.005s 0m6.143s 0m6.115s
多线程 0m6.082s 0m6.144s 0m6.026s 0m5.979s 0m6.012s 0m6.048s

进程线程数:255 / 打印次数:500


第1次 第2次 第3次 第4次 第5次 平均
多进程 0m12.155s 0m12.057s 0m12.433s 0m12.327s 0m11.986s 0m12.184s
多线程 0m12.241s 0m11.956s 0m11.829s 0m12.103s 0m11.928s 0m12.011s

进程线程数:255 / 打印次数:1000


第1次 第2次 第3次 第4次 第5次 平均
多进程 1m2.182s 1m2.635s 1m2.683s 1m2.751s 1m2.694s 1m2.589s
多线程 1m2.622s 1m2.384s 1m2.442s 1m2.458s 1m3.263s 1m2.614s

进程线程数:255 / 打印次数:5000

本轮实验是为了和周丽论文作对比,因此将进程/线程数量限制在255个,论文也是测试了255个进程/线程分别进行10 次,50 次,100 次,200 次……900 次打印的用时,论文得出的结果是:任务量较大时,多进程比多线程效率高;而完成的任务量较小时,多线程比多进程要快,重复打印 600 次时,多进程与多线程所耗费的时间相同

虽然我的实验直到5000打印次数时,多进程才开始领先,但考虑到使用的是NPTL线程库的缘故,从而可以证实了论文的观点。从我的实验数据看,多线程和多进程两组数据非常接近,考虑到数据的提取具有瞬间性,因此可以认为他们的速度是相同的。

当前的网络环境中,我们更看中高并发、高负荷下的性能,纵观前面的实验步骤,最长的实验周期不过1分钟多一点,因此下面的实验将向两个方向延伸,第一,增加并发数量,第二,增加每进程/线程的工作强度。

你可能感兴趣的:(多线程与多进程)