多线程,多进程选择(了解)

鱼还是熊掌:浅谈多进程多线程的选择

关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似的选择问题,那就没有这么简单了,选的不好,会让你深受其害。

经常在网络上看到有的XDJM问“多进程好还是多线程好?”、“Linux下用多进程还是多线程?”等等期望一劳永逸的问题,我只能说:没有最好,只有更好。根据实际情况来判断,哪个更加合适就是哪个好。

我们按照多个不同的维度,来看看多线程和多进程的对比(注:因为是感性的比较,因此都是相对的,不是说一个好得不得了,另外一个差的无法忍受)。


对比维度


多进程


多线程


总结


数据共享、同步


数据共享复杂,需要用IPC;数据是分开的,同步简单


因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂


各有优势


内存、CPU


占用内存多,切换复杂,CPU利用率低


占用内存少,切换简单,CPU利用率高


线程占优


创建销毁、切换


创建销毁、切换复杂,速度慢


创建销毁、切换简单,速度很快


线程占优


编程、调试


编程简单,调试简单


编程复杂,调试复杂


进程占优


可靠性


进程间不会互相影响


一个线程挂掉将导致整个进程挂掉


进程占优


分布式


适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单


适应于多核分布式


进程占优

1)需要频繁创建销毁的优先用线程

原因请看上面的对比。

这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

2)需要进行大量计算的优先使用线程

所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。

这种原则最常见的是图像处理、算法处理。

3)强相关的处理用线程,弱相关的处理用进程

什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。

当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

4)可能要扩展到多机分布的用进程,多核分布的用线程

原因请看上面对比。

5)都满足需求的情况下,用你最熟悉、最拿手的方式

至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。

需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。

消耗资源:

从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

通讯方式:

进程之间传递数据只能是通过通讯的方式,即费时又不方便。线程时间数据大部分共享(线程函数内部不共享),快捷方便。但是数据同步需要锁对于static变量尤其注意

线程自身优势:

提高应用程序响应;使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上;

改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

实验数据:

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

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <signal.h>
  4. #define P_NUMBER 255 //并发进程数量
  5. #define COUNT 5 //每次进程打印字符串数
  6. #define TEST_LOGFILE "logFile.log"
  7. FILE *logFile=NULL;
  8. char *s="hello linux\0";
  9. int main()
  10. {
  11. int i=0,j=0;
  12. logFile=fopen(TEST_LOGFILE,"a+");//打开日志文件
  13. for(i=0;i<P_NUMBER;i++)
  14. {
  15. if(fork()==0)//创建子进程,if(fork()==0){}这段代码是子进程运行区间
  16. {
  17. for(j=0;j<COUNT;j++)
  18. {
  19. printf("[%d]%s\n",j,s);//向控制台输出
  20. /*当你频繁读写文件的时候,Linux内核为了提高读写性能与速度,会将文件在内存中进行缓存,这部分内存就是Cache Memory(缓存内存)。可能导致测试结果不准,所以在此注释*/
  21. //fprintf(logFile,"[%d]%s\n",j,s);//向日志文件输出,
  22. }
  23. exit(0);//子进程结束
  24. }
  25. }
  26. for(i=0;i<P_NUMBER;i++)//回收子进程
  27. {
  28. wait(0);
  29. }
  30. printf("Okay\n");
  31. return 0;
  32. }

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

  1. #include <pthread.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #define P_NUMBER 255//并发线程数量
  6. #define COUNT 5 //每线程打印字符串数
  7. #define TEST_LOG "logFile.log"
  8. FILE *logFile=NULL;
  9. char *s="hello linux\0";
  10. print_hello_linux()//线程执行的函数
  11. {
  12. int i=0;
  13. for(i=0;i<COUNT;i++)
  14. {
  15. printf("[%d]%s\n",i,s);//想控制台输出
  16. /*当你频繁读写文件的时候,Linux内核为了提高读写性能与速度,会将文件在内存中进行缓存,这部分内存就是Cache Memory(缓存内存)。可能导致测试结果不准,所以在此注释*/
  17. //fprintf(logFile,"[%d]%s\n",i,s);//向日志文件输出
  18. }
  19. pthread_exit(0);//线程结束
  20. }
  21. int main()
  22. {
  23. int i=0;
  24. pthread_t pid[P_NUMBER];//线程数组
  25. logFile=fopen(TEST_LOG,"a+");//打开日志文件
  26. for(i=0;i<P_NUMBER;i++)
  27. pthread_create(&pid[i],NULL,(void *)print_hello_linux,NULL);//创建线程
  28. for(i=0;i<P_NUMBER;i++)
  29. pthread_join(pid[i],NULL);//回收线程
  30. printf("Okay\n");
  31. return 0;
  32. }

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

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

time ./fork
time ./thread

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

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

(注:本文平均值算法采用的是去掉一个最大值去掉一个最小值,然后平均)


单核(双核机器禁掉一核),进程/线程数:255,打印次数5


第1次


第2次


第3次


第4次


第5次


平均


多进程


0m0.070s


0m0.071s


0m0.071s


0m0.070s


0m0.070s


0m0.070s


多线程


0m0.049s


0m0.049s


0m0.049s


0m0.049s


0m0.049s


0m0.049s


单核(双核机器禁掉一核),进程/线程数:255,打印次数10


第1次


第2次


第3次


第4次


第5次


平均


多进程


0m0.112s


0m0.101s


0m0.100s


0m0.085s


0m0.121s


0m0.104s


多线程


0m0.097s


0m0.089s


0m0.090s


0m0.104s


0m0.080s


0m0.092s


单核(双核机器禁掉一核),进程/线程数:255,打印次数50


第1次


第2次


第3次


第4次


第5次


平均


多进程


0m0.459s


0m0.531s


0m0.499s


0m0.499s


0m0.524s


0m0.507s


多线程


0m0.387s


0m0.456s


0m0.435s


0m0.423s


0m0.408s


0m0.422s


单核(双核机器禁掉一核),进程/线程数:255,打印次数100


第1次


第2次


第3次


第4次


第5次


平均


多进程


0m1.141s


0m0.992s


0m1.134s


0m1.027s


0m0.965s


0m1.051s


多线程


0m0.925s


0m0.899s


0m0.961s


0m0.934s


0m0.853s


0m0.919s


单核(双核机器禁掉一核),进程/线程数:255,打印次数500


第1次


第2次


第3次


第4次


第5次


平均


多进程


0m5.221s


0m5.258s


0m5.706s


0m5.288s


0m5.455s


0m5.334s


多线程


0m4.689s


0m4.578s


0m4.670s


0m4.566s


0m4.722s


0m4.646s


单核(双核机器禁掉一核),进程/线程数:255,打印次数1000


第1次


第2次


第3次


第4次


第5次


平均


多进程


0m12.680s


0m16.555s


0m11.158s


0m10.922s


0m11.206s


0m11.681s


多线程


0m12.993s


0m13.087s


0m13.082s


0m13.485s


0m13.053s


0m13.074s


单核(双核机器禁掉一核),进程/线程数:255,打印次数5000


第1次


第2次


第3次


第4次


第5次


平均


多进程


1m27.348s


1m5.569s


0m57.275s


1m5.029s


1m15.174s


1m8.591s


多线程


1m25.813s


1m29.299s


1m23.842s


1m18.914s


1m34.872s


1m26.318s


单核(双核机器禁掉一核),进程/线程数:255,打印次数10000


第1次


第2次


第3次


第4次


第5次


平均


多进程


2m8.336s


2m22.999s


2m11.046s


2m30.040s


2m5.752s


2m14.137s


多线程


2m46.666s


2m44.757s


2m34.528s


2m15.018s


2m41.436s


2m40.240s

出的结果是:任务量较大时,多进程比多线程效率高;而完成的任务量较小时,多线程比多进程要快,重复打印 600 次时,多进程与多线程所耗费的时间相同。

、增加每进程/线程的工作强度的实验

这次将程序打印数据增大,原来打印字符串为:

  1. char *s = "hello linux\0";

现在修改为每次打印256个字节数据:

  1. char *s = "1234567890abcdef\
  2. 1234567890abcdef\
  3. 1234567890abcdef\
  4. 1234567890abcdef\
  5. 1234567890abcdef\
  6. 1234567890abcdef\
  7. 1234567890abcdef\
  8. 1234567890abcdef\
  9. 1234567890abcdef\
  10. 1234567890abcdef\
  11. 1234567890abcdef\
  12. 1234567890abcdef\
  13. 1234567890abcdef\
  14. 1234567890abcdef\
  15. 1234567890abcdef\
  16. 1234567890abcdef\0";

单核(双核机器禁掉一核),进程/线程数:255  ,打印次数100


第1次


第2次


第3次


第4次


第5次


平均


多进程


0m6.977s


0m7.358s


0m7.520s


0m7.282s


0m7.218s


0m7.286


多线程


0m7.035s


0m7.552s


0m7.087s


0m7.427s


0m7.257s


0m7.257


单核(双核机器禁掉一核),进程/线程数:  255,打印次数500


第1次


第2次


第3次


第4次


第5次


平均


多进程


0m35.666s


0m36.009s


0m36.532s


0m35.578s


0m41.537s


0m36.069


多线程


0m37.290s


0m35.688s


0m36.377s


0m36.693s


0m36.784s


0m36.618


单核(双核机器禁掉一核),进程/线程数: 255,打印次数1000


第1次


第2次


第3次


第4次


第5次


平均


多进程


1m8.864s


1m11.056s


1m10.273s


1m12.317s


1m20.193s


1m11.215


多线程


1m11.949s


1m13.088s


1m12.291s


1m9.677s


1m12.210s


1m12.150

【实验结论】

从上面的实验比对结果看,即使Linux2.6使用了新的NPTL线程库(据说比原线程库性能提高了很多,唉,又是据说!),多线程比较多进程在效率上没有任何的优势,在线程数增大时多线程程序还出现了运行错误,实验可以得出下面的结论:

在Linux2.6上,多线程并不比多进程速度快,考虑到线程栈的问题,多进程在并发上有优势。

四、多进程和多线程在创建和销毁上的效率比较

预先创建进程或线程可以节省进程或线程的创建、销毁时间,在实际的应用中很多程序使用了这样的策略,比如Apapche预先创建进程、Tomcat 预先创建线程,通常叫做进程池或线程池。在大部分人的概念中,进程或线程的创建、销毁是比较耗时的,在stevesn的著作《Unix网络编程》中有这样 的对比图(第一卷 第三版 30章 客户/服务器程序设计范式):

行号 服务器描述 进程控制CPU时间(秒,与基准之差)
Solaris2.5.1 Digital Unix4.0b BSD/OS3.0
0 迭代服务器(基准测试,无进程控制) 0.0 0.0 0.0
1 简单并发服务,为每个客户请求fork一个进程 504.2 168.9 29.6
2 预先派生子进程,每个子进程调用accept   6.2 1.8
3 预先派生子进程,用文件锁保护accept 25.2 10.0 2.7
4 预先派生子进程,用线程互斥锁保护accept 21.5    
5 预先派生子进程,由父进程向子进程传递套接字 36.7 10.9 6.1
6 并发服务,为每个客户请求创建一个线程 18.7 4.7  
7 预先创建线程,用互斥锁保护accept 8.6 3.5  
8 预先创建线程,由主线程调用accept 14.5 5.0  

stevens已驾鹤西去多年,但《Unix网络编程》一书仍具有巨大的影响力,上表中stevens比较了三种服务器上多进程和多线程的执行效 率,因为三种服务器所用计算机不同,表中数据只能纵向比较,而横向无可比性,stevens在书中提供了这些测试程序的源码(也可以在网上下载)。书中介 绍了测试环境,两台与服务器处于同一子网的客户机,每个客户并发5个进程(服务器同一时间最多10个连接),每个客户请求从服务器获取4000字节数据, 预先派生子进程或线程的数量是15个。

第0行是迭代模式的基准测试程序,服务器程序只有一个进程在运行(同一时间只能处理一个客户请求),因为没有进程或线程的调度切换,因此它的速度是 最快的,表中其他服务模式的运行数值是比迭代模式多出的差值。迭代模式很少用到,在现有的互联网服务中,DNS、NTP服务有它的影子。第1~5行是多进 程服务模式,期中第1行使用现场fork子进程,2~5行都是预先创建15个子进程模式,在多进程程序中套接字传递不太容易(相对于多线 程),stevens在这里提供了4个不同的处理accept的方法。6~8行是多线程服务模式,第6行是现场为客户请求创建子线程,7~8行是预先创建 15个线程。表中有的格子是空白的,是因为这个系统不支持此种模式,比如当年的BSD不支持线程,因此BSD上多线程的数据都是空白的。

从数据的比对看,现场为每客户fork一个进程的方式是最慢的,差不多有20倍的速度差异,Solaris上的现场fork和预先创建子进程的最大差别是504.2 :21.5,但我们不能理解为预先创建模式比现场fork快20倍,原因有两个:

1. stevens的测试已是十几年前的了,现在的OS和CPU已起了翻天覆地的变化,表中的数值需要重新测试。

2. stevens没有提供服务器程序整体的运行计时,我们无法理解504.2 :21.5的实际运行效率,有可能是1504.2 : 1021.5,也可能是100503.2 : 100021.5,20倍的差异可能很大,也可能可以忽略。

因此我写了下面的实验程序,来计算在Linux2.6上创建、销毁10万个进程/线程的绝对用时。

创建10万个进程(forkcreat.c):

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <sys/stat.h>
  6. #include <fcntl.h>
  7. #include <sys/types.h>
  8. #include <sys/wait.h>
  9. int count;//子进程创建成功数量
  10. int fcount;//子进程创建失败数量
  11. int scount;//子进程回收数量
  12. /*信号处理函数–子进程关闭收集*/
  13. void sig_chld(int signo)
  14. {
  15. pid_t chldpid;//子进程id
  16. int stat;//子进程的终止状态
  17. //子进程回收,避免出现僵尸进程
  18. while((chldpid=wait(&stat)>0))
  19. {
  20. scount++;
  21. }
  22. }
  23. int main()
  24. {
  25. //注册子进程回收信号处理函数
  26. signal(SIGCHLD,sig_chld);
  27. int i;
  28. for(i=0;i<100000;i++)//fork()10万个子进程
  29. {
  30. pid_t pid=fork();
  31. if(pid==-1)//子进程创建失败
  32. {
  33. fcount++;
  34. }
  35. else if(pid>0)//子进程创建成功
  36. {
  37. count++;
  38. }
  39. else if(pid==0)//子进程执行过程
  40. {
  41. exit(0);
  42. }
  43. }
  44. printf("count:%d fount:%d scount:%d\n",count,fcount,scount);
  45. }

创建10万个线程(pthreadcreat.c):

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. int count=0;//成功创建线程数量
  4. void thread(void)
  5. {
  6. //啥也不做
  7. }
  8. int main(void)
  9. {
  10. pthread_t id;//线程id
  11. int i,ret;
  12. for(i=0;i<100000;i++)//创建10万个线程
  13. {
  14. ret=pthread_create(&id,NULL,(void *)thread,NULL);
  15. if(ret!=0)
  16. {
  17. printf("Create pthread error!\n");
  18. return(1);
  19. }
  20. count++;
  21. pthread_join(id,NULL);
  22. }
  23. printf("count:%d\n",count);
  24. }

创建10万个线程的Java程序:

  1. public class ThreadTest
  2. {
  3. public static void main(String[] ags) throws InterruptedException
  4. {
  5. System.out.println("开始运行");
  6. long start = System.currentTimeMillis();
  7. for(int i = 0; i < 100000; i++) //创建10万个线程
  8. {
  9. Thread athread = new Thread(); //创建线程对象
  10. athread.start(); //启动线程
  11. athread.join(); //等待该线程停止
  12. }
  13. System.out.println("用时:" + (System.currentTimeMillis() – start) + " 毫秒");
  14. }
  15. }

单核(双核机器禁掉一核),创建销毁10万个进程/线程


第1次


第2次


第3次


第4次


第5次


平均


多进程


0m8.774s


0m8.780s


0m8.475s


0m8.592s


0m8.687s


0m8.684


多线程


0m0.663s


0m0.660s


0m0.662s


0m0.660s


0m0.661s


0m0.661

创建销毁10万个线程(Java)
12286毫秒

从数据可以看出,多线程比多进程在效率上有10多倍的优势,但不能让我们在使用哪种并发模式上定性,这让我想起多年前政治课上的一个场景:在讲到优越性时,面对着几个对此发表质疑评论的调皮男生,我们的政治老师发表了高见,“不能只横向地和当今的发达国家比,你应该纵向地和过去中国几十年的发展历史 比”。政治老师的话套用在当前简直就是真理,我们看看,即使是在赛扬CPU上,创建、销毁进程/线程的速度都是空前的,可以说是有质的飞跃的,平均创建销毁一个进程的速度是0.18毫秒,对于当前服务器几百、几千的并发量,还有预先派生子进程/线程的必要吗?

预先派生子进程/线程比现场创建子进程/线程要复杂很多,不仅要对池中进程/线程数量进行动态管理,还要解决多进程/多线程对accept的“抢” 问题,在stevens的测试程序中,使用了“惊群”和“锁”技术。即使stevens的数据表格中,预先派生线程也不见得比现场创建线程快,在 《Unix网络编程》第三版中,新作者参照stevens的测试也提供了一组数据,在这组数据中,现场创建线程模式比预先派生线程模式已有了效率上的优势。因此我对这一节实验下的结论是:

预先派生进程/线程的模式(进程池、线程池)技术,不仅复杂,在效率上也无优势,在新的应用中可以放心大胆地为客户连接请求去现场创建进程和线程。

我想,这是fork迷们最愿意看到的结论了。

五、双核系统重复周丽论文实验步骤


双核,进程/线程数:255 ,打印次数10


第1次


第2次


第3次


第4次


第5次


平均(单核倍数)


多进程


0m0.061s


0m0.053s


0m0.068s


0m0.061s


0m0.059s


0m0.060(1.73)


多线程


0m0.054s


0m0.040s


0m0.053s


0m0.056s


0m0.042s


0m0.050(1.84)


双核,进程/线程数: 255,打印次数100


第1次


第2次


第3次


第4次


第5次


平均(单核倍数)


多进程


0m0.918s


0m1.198s


0m1.241s


0m1.017s


0m1.172s


0m1.129(0.93)


多线程


0m0.897s


0m1.166s


0m1.091s


0m1.360s


0m0.997s


0m1.085(0.85)


双核,进程/线程数: 255,打印次数1000


第1次


第2次


第3次


第4次


第5次


平均(单核倍数)


多进程


0m11.276s


0m11.269s


0m11.218s


0m10.919s


0m11.201s


0m11.229(1.04)


多线程


0m11.525s


0m11.984s


0m11.715s


0m11.433s


0m10.966s


0m11.558(1.13)


双核,进程/线程数:255 ,打印次数10000


第1次


第2次


第3次


第4次


第5次


平均(单核倍数)


多进程


1m54.328s


1m54.748s


1m54.807s


1m55.950s


1m57.655s


1m55.168(1.16)


多线程


2m3.021s


1m57.611s


1m59.139s


1m58.297s


1m57.258s


1m58.349(1.35)

【实验结论】

双核处理器在完成任务量较少时,没有系统其他瓶颈因素影响时基本上是单核的两倍,在任务量较多时,受系统其他瓶颈因素的影响,速度明显趋近于单核的速度。

六、并发服务的不可测性

看到这里,你会感觉到我有挺进程、贬线程的论调,实际上对于现实中的并发服务具有不可测性,前面的实验和结论只可做参考,而不可定性。对于不可测性,我举个生活中的例子。

这几年在大都市生活的朋友都感觉城市交通状况越来越差,到处堵车,从好的方面想这不正反应了我国GDP的高速发展。如果你7、8年前来到西安市,穿 过南二环上的一些十字路口时,会发现一个奇怪的U型弯的交通管制,为了更好的说明,我画了两张图来说明,第一张图是采用U型弯之前的,第二张是采用U型弯 之后的。

南二环交通图一

南二环交通图二

为了讲述的方便,我们不考虑十字路口左拐的情况,在图一中东西向和南北向的车辆交汇在十字路口,用红绿灯控制同一时间只能东西向或南北向通行,一般 的十字路口都是这样管控的。随着车辆的增多,十字路口的堵塞越来越严重,尤其是上下班时间经常出现堵死现象。于是交通部门在不动用过多经费的情况下而采用 了图二的交通管制,东西向车辆行进方式不变,而南北向车辆不能直行,需要右拐到下一个路口拐一个超大的U型弯,这样的措施避免了因车辆交错而引发堵死的次 数,从而提高了车辆的通过效率。我曾经问一个每天上下班乘公交经过此路口的同事,他说这样的改动不一定每次上下班时间都能缩短,但上班时间有保障了,从而 迟到次数减少了。如果今天你去西安市的南二环已经见不到U型弯了,东西向建设了高架桥,车辆分流后下层的十字路口已恢复为图一方式。

从效率的角度分析,在图一中等一个红灯45秒,远远小于图二拐那个U型弯用去的时间,但实际情况正好相反。我们可以设想一下,如果路上的所有运行车 辆都是同一型号(比如说全是QQ3微型车),所有的司机都遵守交规,具有同样的心情和性格,那么图一的通行效率肯定比图二高。现实中就不一样了,首先车辆 不统一,有大车、小车、快车、慢车,其次司机的品行不一,有特别遵守交规的,有想耍点小聪明的,有性子慢的,也有的性子急,时不时还有三轮摩托逆行一下, 十字路口的“死锁”也就难免了。

那么在什么情况下图二优于图一,是否能拿出一个科学分析数据来呢?以现在的科学技术水平是拿不出来的,就像长期的天气预报不可预测一样,西安市的交管部门肯定不是分析各种车辆的运行规律、速度,再进行复杂的社会学、心理学分析做出U型弯的决定的,这就是要说的不可测性。

现实中的程序亦然如此,比如WEB服务器,有的客户在快车道(宽带),有的在慢车道(窄带),有的性子慢(等待半分钟也无所谓),有的性子急(拼命 的进行浏览器刷新),时不时还有一两个黑客混入其中,这种情况每个服务器都不一样,既是是同一服务器每时每刻的变化也不一样,因此说不具有可测性。开发者 和维护者能做的,不论是前面的这种实验测试,还是对具体网站进行的压力测试,最多也就能模拟相当于QQ3通过十字路口的场景。

结束语

本篇文章比较了Linux系统上多线程和多进程的运行效率,在实际应用时还有其他因素的影响,比如网络通讯时采用长连接还是短连接,是否采用 select、poll,java中称为nio的机制,还有使用的编程语言,例如Java不能使用多进程,PHP不能使用多线程,这些都可能影响到并发模 式的选型。

最后还有两点提醒:

1. 文章中的所有实验数据有环境约束。
2. 由于并行服务的不可测性,文章中的观点应该只做参考,而不要去定性。

其实很晕的,项目里面线程很多,进程就两个,下面的底层通信一个进程。还需多加练习。

多线程,多进程选择(了解)

时间: 2024-10-19 14:19:41

多线程,多进程选择(了解)的相关文章

Python多线程多进程那些事儿看这篇就够了~~

自己以前也写过多线程,发现都是零零碎碎,这篇写写详细点,填一下GIL和Python多线程多进程的坑~ 总结下GIL的坑和python多线程多进程分别应用场景(IO密集.计算密集)以及具体实现的代码模块. 目录   0x01 进程 and 线程 and “GIL” 0x02 python多线程&&线程锁&&threading类 0x03 python队列代码实现 0x04 python之线程池实现 0x05 python多进程并行实现 0x01 进程 and 线程 and “

Python有了asyncio和aiohttp在爬虫这类型IO任务中多线程/多进程还有存在的必要吗?

最近正在学习Python中的异步编程,看了一些博客后做了一些小测验:对比asyncio+aiohttp的爬虫和asyncio+aiohttp+concurrent.futures(线程池/进程池)在效率中的差异,注释:在爬虫中我几乎没有使用任何计算性任务,为了探测异步的性能,全部都只是做了网络IO请求,就是说aiohttp把网页get完就程序就done了. 结果发现前者的效率比后者还要高.我询问了另外一位博主,(提供代码的博主没回我信息),他说使用concurrent.futures的话因为我全

多线程多进程基础

看廖雪峰的多线程多进程教程看的云里雾里的,google了一下,贴上来点重点. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间.多任务操作系统可以“并发”执行这些进程. 线程是指进程中乱序.多次执行的代码块,多个线程可以“同时”运行,所以认为多个线程是“并发”的. 多线程vs多进程 操作系统会为每个进程分配不同的内存块,而多个线程共享进程的内存块.这带来最直接的不同就是创建线程的开销远小于创建进程的开销. 同时,由于内存块不同,所以进程之间的通信相对困难.需要采用pipe/n

Git的使用及网络编程多线程多进程

Git的使用 1.打开CMD命令行,输入cd Desktop进入桌面 2.输入 mkdir + 'file name'创建文件,如果已有项目则输入 cd + file name进入文件,如果在Git上已有项目并且在已有项目进行修改,则输入cd clone + url 3.如果第一次使用,则先初始化,git init 4. git add + file name 把项目加入文件夹里面 5.git config -global 全局设置(第一次使用的时候设置) 6. git log 查看日志.git

多进程与多线程的选择(转)

1各自特色 关于线程和进程,我们上大学时的教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”.当然了,这句话应付考试已经够了,但是在工作中,光知道这句话是一点用都没有的. 我们在做程序设计的时候,会纠结是用多线程还是用多进程,我可以告诉你,这个问题没有标准答案,合理即正确.根据实际的项目需求,哪个合适选择哪一个. 下面对比一下多进程和多线程的区别 对比项 多进程 多线程 结论 数据共享 数据共享复杂,需要使用IPC,同步简单 数据是共享的,同步复杂 各有优势 内存.

线程 进程 多线程 多进程

进程和线程的主要区别在于多进程每个进程拥有独立存储空间,而多线程共享存储空间.对于单核CPU来讲,如果是阻塞操作,或者不耗时非阻塞操作,多进程/线程不会提高效率,这时候多进程/线程最有用的通常是耗时而又非阻塞的I/O操作. 打个比喻,一个人要看两部电影,可以看完一部再看另一部,也可以同时看,看一眼这个暂停,看一眼那个再暂停看回前一个,快速不停切换,你会觉得因为两部一起看所以先看完吗?理论上两部电影播放时间加起来是一样,所以看完所用时间应该一样.但是实际使用时间反而可能第一种方法快,为什么?切换是

什么是多线程 多进程

1:要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在. 2:什么是进程? 通过任务管理器我们就看到了进程的存在. 而通过观察,我们发现只有运行的程序才会出现进程. 进程:就是正在运行的程序. 进程是系统进行资源分配和调用的独立单位.每一个进程都有它自己的内存空间和系统资源. 3:多进程有什么意义呢? 单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情. 举例:一边玩游戏(游戏进程),一边听音乐(音乐进程). 也就是说现在的计算机都是支持多进

python - 多线程/多进程

多线程: import threading from multiprocessing import Queue from time import sleep from bs4 import BeautifulSoup from requests import get import re class myThread(threading.Thread): def __init__(self, qlock, queue): threading.Thread.__init__(self) self.q

多线程&amp;多进程

一.多线程 概念 进程是程序在计算机上的一次执行活动.当你运行一个程序,你就启动了一个进程.显然,程序是死的(静态的),进程是活的(动态的).进程可以分为系统进程和用户进程.凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身:用户进程就不必我多讲了吧,所有由你启动的进程都是用户进程.进程是操作系统进行资源分配的单位. 它的思想简单介绍如下:在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉