多进程和多线程区别_进程和线程的区别 多进程和多线程的区别 - CSDN
精华内容
参与话题
  • 面试总结,多进程和多线程区别

    万次阅读 多人点赞 2018-12-30 11:20:15
    关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”。这句话应付考试基本上够了,但如果在工作中遇到类似的选择问题,那就没有那么简单了,选的不好,会让你深受...

    原文链接:http://blog.csdn.net/bing_bing/article/details/8093552

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

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

    维度

    多进程

    多线程

    总结

    数据共享、同步

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

    多线程共享进程数据:共享简单;同步复杂

    各有优势

    内存、CPU

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

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

    线程占优

    创建销毁、切换

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

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

    线程占优 

    编程调试

    编程简单,调试简单

    编程复杂,调试复杂

    进程占优 

    可靠性

    进程间不会相互影响 

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

    进程占优

    分布式 

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

    适应于多核分布

    进程占优

     

     

    然后我们来看下线程和进程间的比较

     

    子进程继承父进程的属性:

    子线程继承主线程的属性:

    实际用户ID,实际组ID,有效用户ID,有效组ID;

    附加组ID;

    进程组ID;

    会话ID;

    控制终端;

    设置用户ID标志和设置组ID标志;

    当前工作目录;

    根目录;

    文件模式创建屏蔽字(umask);

    信号屏蔽和安排;

    针对任一打开文件描述符的在执行时关闭(close-on-exec)标志;

    环境;

    连接的共享存储段;

    存储映射;

    资源限制;

    进程中的所有信息对该进程的所有线程都是共享的;

    可执行的程序文本;

    程序的全局内存;

    堆内存;

    栈;

    文件描述符;

    信号的处理是进程中所有线程共享的(注意:如果信号的默认处理是终止该进程那么即是把信号传给某个线程也一样会将进程杀掉);

     

    父子进程之间的区别:

    子线程特有的:

    fork的返回值(=0子进程);

    进程ID不同;

    两个进程具有不同的父进程ID;

    子进程的tms_utime,tms_stime,tms_cutime以及tms_ustime均被设置为0;

    不继承父进程设置的文件锁;

    子进程的未处理闹钟被清除;

    子进程的未处理信号集设置为空集;

    线程ID;

    一组寄存器值;

    栈;

    调度优先级和策略;

    信号屏蔽字;

    errno变量;

    线程私有数据;

     


    1)需要频繁创建销毁的优先用线程。
    实例:web服务器。来一个建立一个线程,断了就销毁线程。要是用进程,创建和销毁的代价是很难承受的。
    2)需要进行大量计算的优先使用线程。
    所谓大量计算,当然就是要消耗很多cpu,切换频繁了,这种情况先线程是最合适的。
    实例:图像处理、算法处理
    3)强相关的处理用线程,若相关的处理用进程。
    什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
    一般的server需要完成如下任务:消息收发和消息处理。消息收发和消息处理就是弱相关的任务,而消息处理里面可能又分为消息解码、业务处理,这两个任务相对来说相关性就要强多了。因此消息收发和消息处理可以分进程设计,消息解码和业务处理可以分线程设计。
    4)可能扩展到多机分布的用进程,多核分布的用线程。
    5)都满足需求的情况下,用你最熟悉、最拿手的方式。

     

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

     

    展开全文
  • 多线程和多进程区别

    千次阅读 2018-05-03 16:28:26
    线程:是进程的一个实体,是CPU调度分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器栈),但是它...

    1.定义

    程序: 只是一组指令的有序集合,是计算机硬盘上的一些文件,是“死的”

    进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位,是“活的”

    线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

    这些定义概念不太好理解.引用某些知乎大牛上解释:

    首先来一句概括的总论:进程和线程都是一个时间段的描述,是CPU工作时间段的描述。

    下面细说背景:

    CPU+RAM+各种资源(比如显卡,光驱,键盘,GPS, 等等外设)构成我们的电脑,但是电脑的运行,实际就是CPU和相关寄存器以及RAM之间的事情。

    一个最最基础的事实:

    CPU太快,太快,太快了,寄存器仅仅能够追的上他的脚步,RAM和别的挂在各总线上的设备完全是望其项背。那当多个任务要执行的时候怎么办呢?轮流着来?或者谁优先级高谁来?不管怎么样的策略,一句话就是在CPU看来就是轮流着来。

    一个必须知道的事实:

    执行一段程序代码,实现一个功能的过程介绍 ,当得到CPU的时候,相关的资源必须也已经就位,就是显卡啊,GPS啊什么的必须就位,然后CPU开始执行。这里除了CPU以外所有的就构成了这个程序的执行环境,也就是我们所定义的程序上下文。当这个程序执行完了,或者分配给他的CPU执行时间用完了,那它就要被切换出去,等待下一次CPU的临幸。在被切换出去的最后一步工作就是保存程序上下文,因为这个是下次他被CPU临幸的运行环境,必须保存。串联起来的事实:前面讲过在CPU看来所有的任务都是一个一个的轮流执行的,

    具体的轮流方法就是:先加载程序A的上下文,然后开始执行A,保存程序A的上下文,调入下一个要执行的程序B的程序上下文,然后开始执行B,保存程序B的上下文。。。。

    ========= 重要的东西出现了=============

    进程和线程就是这样的背景出来的,两个名词不过是对应的CPU时间段的描述,名词就是这样的功能。进程就是包换上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文线程是什么呢?进程的颗粒度太大,每次都要有上下的调入,保存,调出。如果我们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不可能是一条逻辑执行的,必定有多个分支和多个程序段,就好比要实现程序A,实际分成 a,b,c等多个块组合而成。那么这里具体的执行就可能变成:程序A得到CPU =》CPU加载上下文,开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段,最后CPU保存A的上下文。这里a,b,c的执行是共享了A的上下文,CPU在执行的时候没有进行上下文切换的。这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境,的更为细小的CPU时间段。到此全文结束,再一个总结:

    进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同。

    开个QQ,开了一个进程;开了迅雷,开了一个进程。在QQ的这个进程里,传输文字开一个线程、传输语音开了一个线程、弹出对话框又开了一个线程。所以运行某个软件,相当于开了一个进程。在这个软件运行的过程里(在这个进程里),多个工作支撑的完成QQ的运行,那么这“多个工作”分别有一个线程。所以一个进程管着多个线程。通俗的讲:“进程是爹妈,管着众多的线程儿子”…

    2.关系

    一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行(轮流获取cpu的时间片,在总体上给人的感觉是同时在执行,这是并发,还有一个名词叫并行,就是多核多cpu,真正的同时在执行,不过现在实现难度有点大,在用处上主要用在并行计算),至少要有一个线程

    相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

    3.区别

    进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

    1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

    2) 线程的划分尺度小于进程,使得多线程程序的并发性高。

    3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

    4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

    5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

    4.优缺点

    线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

    展开全文
  • 多进程和多线程的主要区别是:线程是进程的子集(部分),一个进程可能由多个线程组成。多进程的数据是分开的、共享复杂,需要用IPC;但同步简单。多线程共享进程数据,共享简单;但同步复杂。  多进程,Windows...

     

    多进程和多线程的主要区别是:线程是进程的子集(部分),一个进程可能由多个线程组成。多进程的数据是分开的、共享复杂,需要用IPC;但同步简单。多线程共享进程数据,共享简单;但同步复杂。

      多进程,Windows 应用程序中消息有两种送出途径;直接和排队。Windows或某些运行的应用程序可直接发布消息给窗口过程,或者,消息可送到消息列象连续不断轮询消息队列的OS中当前执行的每个进程都 事件驱动程序不是由事件的顺序来控制,而是由事件的发生来控,而事件的发生是随机的、不确定的,这就允许程序的用户用各种合理的顺序来安排程序的流程。

      多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器。[1] 在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理(Multithreading)”。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程(台湾译作“执行绪”),进而提升整体处理性能。

    一、多进程和多线程的区别是什么?

      在Linux下编程多用多进程编程少用多线程编程。

      IBM有个家伙做了个测试,发现切换线程context的时候,windows比linux快一倍多。进出最快的锁(windows2k的 critical section和linux的pthread_mutex),windows比linux的要快五倍左右。当然这并不是说linux不好,而且在经过实际编程之后,综合来看我觉得linux更适合做high performance server,不过在多线程这个具体的领域内,linux还是稍逊windows一点。这应该是情有可原的,毕竟unix家族都是从多进程过来的,而 windows从头就是多线程的。

      如果是UNIX/linux环境,采用多线程没必要。

      多线程比多进程性能高?误导!

      应该说,多线程比多进程成本低,但性能更低。

      在UNIX环境,多进程调度开销比多线程调度开销,没有显著区别,就是说,UNIX进程调度效率是很高的。内存消耗方面,二者只差全局数据区,现在内存都很便宜,服务器内存动辄若干G,根本不是问题。

      多进程是立体交通系统,虽然造价高,上坡下坡多耗点油,但是不堵车。

      多线程是平面交通系统,造价低,但红绿灯太多,老堵车。

      我们现在都开跑车,油(主频)有的是,不怕上坡下坡,就怕堵车。

      高性能交易服务器中间件,如TUXEDO,都是主张多进程的。实际测试表明,TUXEDO性能和并发效率是非常高的。TUXEDO是贝尔实验室的,与UNIX同宗,应该是对UNIX理解最为深刻的,他们的意见应该具有很大的参考意义。

      二、多进程和多线程的优缺点分析:

      多进程:

      多进程优点:

      1、每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;

      2、通过增加CPU,就可以容易扩充性能;

      3、可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;

      4、每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大。

      多进程缺点:

      1、逻辑控制复杂,需要和主程序交互;

      2、需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大;

      3、最好是多进程和多线程结合,即根据实际的需要,每个CPU开启一个子进程,这个子进程开启多线程可以为若干同类型的数据进行处理。当然你也可以利用多线程+多CPU+轮询方式来解决问题……

      4、方法和手段是多样的,关键是自己看起来实现方便有能够满足要求,代价也合适。

      多线程:

      多线程的优点:

      1、无需跨进程边界;

      2、程序逻辑和控制方式简单;

      3、所有线程可以直接共享内存和变量等;

      4、线程方式消耗的总资源比进程方式好。

      多线程缺点:

      1、每个线程与主程序共用地址空间,受限于2GB地址空间;

      2、线程之间的同步和加锁控制比较麻烦;

      3、一个线程的崩溃可能影响到整个程序的稳定性;

      4、到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;

      5、线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU。

    参考:

    多进程和多线程的区别是什么​? 

    展开全文
  • 多线程还是多进程的选择及区别

    万次阅读 多人点赞 2013-08-28 15:36:18
    鱼还是熊掌:浅谈多进程多线程的选择 关于多进程多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似的选择问题,那就...

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

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

     

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

     

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

    对比维度

    多进程

    多线程

    总结

    数据共享、同步

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

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

    各有优势

    内存、CPU

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

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

    线程占优

    创建销毁、切换

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

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

    线程占优

    编程、调试

    编程简单,调试简单

    编程复杂,调试复杂

    进程占优

    可靠性

    进程间不会互相影响

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

    进程占优

    分布式

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

    适应于多核分布式

    进程占优

     

    看起来比较简单,优势对比上是“线程 3.5 v 2.5 进程”,我们只管选线程就是了?

     

    呵呵,有这么简单我就不用在这里浪费口舌了,还是那句话,没有绝对的好与坏,只有哪个更加合适的问题。我们来看实际应用中究竟如何判断更加合适。

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

    原因请看上面的对比。

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

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

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

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

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

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

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

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

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

    原因请看上面对比。

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

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

     

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


    1、进程与线程

    进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位

    线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。

    "进程——资源分配的最小单位,线程——程序执行的最小单位"

    进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

     

    总的来说就是:进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。(下面的内容摘自Linux下的多线程编程

    使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

    使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

    除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

    • 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
    • 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
    • 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。


    在Unix上编程采用多线程还是多进程的争执由来已久,这种争执最常见到在B/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。我的环境是:双核 Intel(R) Xeon(R) CPU 5130  @ 2.00GHz(做实验时,禁掉了一核),512MG内存,Red Hat Enterprise Linux ES release 4 (Nahant Update 4),内核2.6.9-42。

    进程实验代码(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.     
    27.     for(i=0;i<P_NUMBER;i++)//回收子进程
    28.     {
    29.         wait(0);
    30.     }
    31.     
    32.     printf("Okay\n");
    33.     return 0;
    34. }

    进程实验代码(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.     
    27.     for(i=0;i<P_NUMBER;i++)
    28.         pthread_create(&pid[i],NULL,(void *)print_hello_linux,NULL);//创建线程
    29.         
    30.     for(i=0;i<P_NUMBER;i++)
    31.         pthread_join(pid[i],NULL);//回收线程
    32.         
    33.     printf("Okay\n");
    34.     return 0;
    35. }

    两段程序做的事情是一样的,都是创建“若干”个进程/线程,每个创建出的进程/线程打印“若干”条“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 


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

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

    是不是可以得出这样的结论:多线程创建、销毁速度快,而多线程切换速度快,这个结论我们会在第二个试验中继续试图验证

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

    二、增加并发数量的实验

    下面的实验打印次数不变,而进程/线程数量逐渐增加。在实验过程中多线程程序在后四组(线程数350,500,800,1000)的测试中都出现了“段错误”,出现错误的原因和多线程预分配线程栈有关。

    实验中的计算机CPU是32位,寻址最大范围是4GB(2的32次方),Linux是按照3GB/1GB的方式来分配内存,其中1GB属于所有进程共享的内核空间,3GB属于用户空间(进程虚拟内存空间)。Linux2.6的默认线程栈大小是8M(通过ulimit -a查看),对于多线程,在创建线程的时候系统会为每一个线程预分配线程栈地址空间,也就是8M的虚拟内存空间。线程数量太多时,线程栈累计的大小将超过进程虚拟内存空间大小(计算时需要排除程序文本、数据、共享库等占用的空间),这就是实验中出现的“段错误”的原因。

    Linux2.6的默认线程栈大小可以通过 ulimit -s 命令查看或修改,我们可以计算出线程数的最大上线: (1024*1024*1024*3) / (1024*1024*8) = 384,实际数字应该略小与384,因为还要计算程序文本、数据、共享库等占用的空间。在当今的稍显繁忙的WEB服务器上,突破384的并发访问并不是稀 罕的事情,要继续下面的实验需要将默认线程栈的大小减小,但这样做有一定的风险,比如线程中的函数分配了大量的自动变量或者函数涉及很深的栈帧(典型的是 递归调用),线程栈就可能不够用了。可以配合使用POSIX.1规定的两个线程属性guardsize和stackaddr来解决线程栈溢出问 题,guardsize控制着线程栈末尾之后的一篇内存区域,一旦线程栈在使用中溢出并到达了这片内存,程序可以捕获系统内核发出的告警信号,然后使用 malloc获取另外的内存,并通过stackaddr改变线程栈的位置,以获得额外的栈空间,这个动态扩展栈空间办法需要手工编程,而且非常麻烦。

    有两种方法可以改变线程栈的大小,使用 ulimit -s 命令改变系统默认线程栈的大小,或者在代码中创建线程时通过pthread_attr_setstacksize函数改变栈尺寸,在实验中使用的是第一种,在程序运行前先执行ulimit指令将默认线程栈大小改为1M:

    ulimit -s 1024
    time ./thread

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

     

    1

    2

    3

    4

    5

    平均

    多进程

     0m3.834s

     0m3.759s

     0m4.376s

     0m3.936s

     0m3.851s

     0m3.874

    多线程

     0m3.646s

    0m4.498s

     0m4.219s

     0m3.893s

     0m3.943s

     0m4.018

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

     

    1

    2

    3

    4

    5

    平均

    多进程

     0m9.731s

     0m9.833s

     0m10.046s

     0m9.830s

     0m9.866s

     0m9.843s

    多线程

     0m9.961s

     0m9.699s

     0m9.958s

     0m10.111s

     0m9.686s

     0m9.873s


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

     

    1

    2

    3

    4

    5

    平均

    多进程

     0m13.773s

     0m13.500s

     0m13.519s

     0m13.474s

     0m13.351s

     0m13.498

    多线程

     0m12.754s

    0m13.251s 

     0m12.813s

     0m16.861s

     0m12.764s

     0m12.943


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

     

    1

    2

    3

    4

    5

    平均

    多进程

     0m23.762s

     0m22.151s

     0m23.926s

     0m21.327s

     0m21.429s

     0m22.413

    多线程

     0m20.603s

     0m20.291s

     0m21.654s

     0m20.684s

     0m20.671s

     0m20.653


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

     

    1

    2

    3

    4

    5

    平均

    多进程

     0m33.616s

     0m31.757s

     0m31.759s

     0m32.232s

     0m32.498s

     0m32.163

    多线程

     0m32.050s

     0m32.787s

     0m33.055s

     0m32.902s

     0m32.235s

     0m32.641


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

     

    1

    2

    3

    4

    5

    平均

    多进程

     0m40.301s

     0m41.083s

     0m41.634s

     0m40.247s

     0m40.717s

     0m40.700

    多线程

     0m41.633s

     0m41.118s

     0m42.700s

     0m42.134s

     0m41.170s

     0m41.646


    【实验结论】 
    当线程/进程逐渐增多时,执行相同任务时,线程所花费时间相对于进程有下降的趋势(本人怀疑后两组数据受系统其他瓶颈的影响),这是不是进一步验证了多线程创建、销毁速度快,而多进程切换速度快。

    出现了线程栈的问题,让我特别关心Java线程是怎样处理的,因此用Java语言写了同样的实验程序,Java程序加载虚拟机环境比较耗时,所以没 有用time提取测试时间,而直接将测时写入代码。对Linux上的C编程不熟悉的Java程序员也可以用这个程序去对比理解上面的C语言试验程序。
    1. import java.io.File;
    2.     import java.io.FileNotFoundException;
    3.     import java.io.FileOutputStream;
    4.     import java.io.IOException;
    5.      
    6.     public class MyThread extends Thread
    7.     {
    8.         static int P_NUMBER = 1000; /* 并发线程数量 */
    9.         static int COUNT = 1000; /* 每线程打印字符串次数 */
    10.      
    11.         static String s = "hello linux\n";
    12.            
    13.         static FileOutputStream out = null; /* 文件输出流 */
    14.         @Override
    15.         public void run()
    16.         {
    17.             for (int i = 0; i < COUNT; i++)
    18.             {
    19.                 System.out.printf("[%d]%s", i, s); /* 向控制台输出 */
    20.                
    21.                 StringBuilder sb = new StringBuilder(16);
    22.                 sb.append("[").append(i).append("]").append(s);
    23.                 try
    24.                 {
    25.                     out.write(sb.toString().getBytes());/* 向日志文件输出 */
    26.                 }
    27.                 catch (IOException e)
    28.                 {
    29.                     e.printStackTrace();
    30.                 }
    31.             }
    32.         }
    33.      
    34.         public static void main(String[] args) throws FileNotFoundException, InterruptedException
    35.         {
    36.             MyThread[] threads = new MyThread[P_NUMBER]; /* 线程数组 */
    37.            
    38.             File file = new File("Javalogfile.log");
    39.             out = new FileOutputStream(file, true); /* 日志文件输出流 */
    40.            
    41.             System.out.println("开始运行");
    42.             long start = System.currentTimeMillis();
    43.      
    44.             for (int i = 0; i < P_NUMBER; i++) //创建线程
    45.             {
    46.                 threads[i] = new MyThread();
    47.                 threads[i].start();
    48.             }
    49.      
    50.             for (int i = 0; i < P_NUMBER; i++) //回收线程
    51.             {
    52.                 threads[i].join();
    53.             }
    54.            
    55.             System.out.println("用时:" + (System.currentTimeMillis() – start) + " 毫秒");
    56.             return;
    57.         }
    58.       
    59.     }

    进程/线程数:1000  ,打印次数1000(用得原作者的数据)

     

    1

    2

    3

    4

    5

    平均

    多线程

     65664 ms

     66269 ms

     65546ms

     65931ms

     66540ms

     65990 ms

    Java程序比C程序慢一些在情理之中,但Java程序并没有出现线程栈问题,5次测试都平稳完成,可以用下面的ps指令获得java进程中线程的数量:

    diaoyf@ali:~$ ps -eLf | grep MyThread | wc -l
    1010

    用ps测试线程数在1010上维持了很长时间,多出的10个线程应该是jvm内部的管理线程,比如用于GC。我不知道Java创建线程时默认栈的大 小是多少,很多资料说法不统一,于是下载了Java的源码jdk-6u21-fcs-src-b07-jrl-17_jul_2010.jar(实验环境 安装的是 SUN jdk 1.6.0_20-b02),但没能从中找到需要的信息。对于jvm的运行,java提供了控制参数,因此再次测试时,通过下面的参数将Java线程栈大 小定义在8192k,和Linux的默认大小一致:

    diaoyf@ali:~/tmp1$ java -Xss8192k MyThread

    出乎意料的是并没有出现想象中的异常,但用ps侦测线程数最高到达337,我判断程序在创建线程时在栈到达可用内存的上线时就停止继续创建了,程序运行的时间远小于估计值也证明了这个判断。程序虽然没有抛出异常,但运行的并不正常,另一个问题是最后并没有打印出“用时 xxx毫秒”信息。

    这次测试更加深了我的一个长期的猜测:Java的Web容器不稳定。因为我是多年编写B/S的Java程序员,WEB服务不稳定常常挂掉也是司空见惯的,除了自己或项目组成员水平不高,代码编写太烂的原因之外,我一直猜测还有更深层的原因,如果就是线程原因的话,这颠覆性可比本篇文章的多进程性能颠覆性要大得多,想想世界上有多少Tomcat、Jboss、Websphere、weblogic在跑着,嘿嘿。

    这次测试还打破了以前的一个说法:单CPU上并发超过6、7百,线程或进程间的切换就会占用大量CPU时间,造成服务器效率会急剧下降。但从上面的实验来看,进程/线程数到1000时(这差不多是非常繁忙的WEB服务器了),仍具有很好的线性。

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

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

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

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

    1. char *= "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.     //子进程回收,避免出现僵尸进程
    19.     while((chldpid=wait(&stat)>0))
    20.     {
    21.         scount++;
    22.     }
    23. }

    24. int main()
    25. {
    26.     //注册子进程回收信号处理函数
    27.     signal(SIGCHLD,sig_chld);
    28.     
    29.     int i;
    30.     for(i=0;i<100000;i++)//fork()10万个子进程
    31.     {
    32.         pid_t pid=fork();
    33.         if(pid==-1)//子进程创建失败
    34.         {
    35.             fcount++;
    36.         }
    37.         else if(pid>0)//子进程创建成功
    38.         {
    39.             count++;
    40.         }
    41.         else if(pid==0)//子进程执行过程
    42.         {
    43.             exit(0);
    44.         }
    45.     }
    46.     
    47.     printf("count:%d fount:%d scount:%d\n",count,fcount,scount);
    48. }

    创建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.     
    13.     for(i=0;i<100000;i++)//创建10万个线程
    14.     {
    15.         ret=pthread_create(&id,NULL,(void *)thread,NULL);
    16.         if(ret!=0)
    17.         {
    18.             printf("Create pthread error!\n");
    19.             return(1);
    20.         }
    21.         count++;
    22.         pthread_join(id,NULL);
    23.     }
    24.     
    25.     printf("count:%d\n",count);
    26. }

    创建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.            
    14.             System.out.println("用时:" + (System.currentTimeMillis() – start) + " 毫秒");
    15.         }
    16.     }

    单核(双核机器禁掉一核),创建销毁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. 由于并行服务的不可测性,文章中的观点应该只做参考,而不要去定性。

    【参考资料】

    1. 《Linux系统下多线程与多进程性能分析》作者“周丽 焦程波 兰巨龙”,这是我写这篇文章的诱因之一,只是不知道引用原作的程序代码是否属于侵权行为。

    2. stevens著作的《Unix网络编程(第一卷)》和《Unix高级环境编程》,这两本书应该收集入IT的四书五经。

    3. Robert Love著作的《Linux内核设计与实现》。

    4. John Fusco 著作的《Linux开发工具箱》,这本书不太出名,但却是我读过的对内存和进程调度讲解最清晰明了的,第5章“开发者必备内核知识”和第6章“进程”是这本书的精华。

    展开全文
  • 多任务中 多线程和多进程区别

    千次阅读 2019-04-26 14:22:22
    多任务中 多线程和多线程区别
  • 多进程和多线程区别

    2020-03-05 19:16:43
    1. 多进程:每个进程就像系统中的一个业务线,工厂的一个车间。比如你在电脑上,可以边听音乐,边聊qq,打开一个虾米音乐就开启了一个进程,打开一个qq就开启了另外一个进程; 再比如一个溯源系统,一个业务是流水...
  • 1:请问QT的多线程是不是可以理解成Linux下的多进程? 2:如果不是,那么请问Qt下怎么进行多进程编程?进程之间的通信同步互斥什么的Qt有没有提供支持的类?还是得用Linux下的消息队列、管道、信号量什么的? 3:...
  • 进程和线程区别(超详细)

    万次阅读 多人点赞 2019-11-07 15:58:33
    进程和线程 进程 一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有线程,比如在Windows系统中,一个运行的xx.exe就是一个进程线程 进程中的一个执行任务(控制单元),负责...
  • Python之多进程和多线程详解

    万次阅读 多人点赞 2018-10-16 18:02:56
    1.进程的概念 一个CPU的时候运行,轮询调度实现并发执行 CPU运行机制: 计算机程序:存储在磁盘上的可执行二进制(或其他类型)文件。 只有把它们加载到内存中,并被操作系统调用它们才会拥有其自己的生命周期。...
  • 多线程和多进程区别(小结)

    万次阅读 多人点赞 2011-08-15 15:54:54
    很想写点关于多进程和多线程的东西,我确实很爱他们。但是每每想动手写点关于他们的东西,却总是求全心理作祟,始终动不了手。今天终于下了决心,写点东西,以后可以再修修补补也无妨。 一.为何需要多进程(或者...
  • 线程、进程、多线程多进程 多任务 小结

    千次阅读 多人点赞 2019-07-02 13:40:27
    6 线程进程的区别 7 进程的优缺点 7.1 进程的优点 7.2 进程的缺点 8 线程的优缺点 8.1 线程的优点 8.2 线程的缺点 9 多线程的优缺点 9.1 多线程的优点 9.2 多线程的缺点 10多进程的优缺点 10.1 多进程...
  • 很想写点关于多进程和多线程的东西,我确实很爱他们。但是每每想动手写点关于他们的东西,却总是求全心理作祟,始终动不了手。 今天终于下了决心,写点东西,以后可以再修修补补也无妨。一.为何需要多进程(或者...
  • 线程和进程 概念 进程(process):是指具有已一定功能的独立程序,是系统资源分配的基本单位,在内存中有其完备的数据空间代码空间,拥有完整的虚拟空间地址。一个进程所拥有的数据变量只属于它自己。 线程...
  • 线程是什么?要理解这个概念,须要先了解一下操作系统的一些相关概念。大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,...
  • 多进程和多线程的优缺点

    万次阅读 2013-09-27 18:47:24
    在Linux下编程多用多进程编程少用多线程编程。  IBM有个家伙做了个测试,发现切换线程context的时候,windows比linux快一倍多。进出最快的锁(windows2k的 critical sectionlinux的pthread_mutex),windows比...
  • 谈谈dpdk应用层包处理程序的多进程和多线程模型选择时的若干考虑 看到知乎上有个关于linux多进程多线程的讨论:链接地址 自己项目里也对这个问题有过很多探讨测试,所以正好开贴整理一下,题目有点长,其实就...
  • python中多进程和多线程的配合使用

    千次阅读 2017-08-09 06:52:17
    由于python的多线程中存在PIL锁,因此python的多线程不能利用多核,那么,由于现在的计算机是多核的,就不能充分利用计算机的多核资源。但是python中的多进程是可以跑在不同的cpu上的。因此,尝试了多进程+多线程的...
  • python 彻底解读多线程多进程

    万次阅读 多人点赞 2019-03-26 14:20:34
    title: 多线程多进程 copyright: true top: 0 date: 2019-03-03 16:16:41 tags: 多线程多进程 categories: Python高阶笔记 permalink: password: keywords: description: 对python的多线程多进程进一步刨析。 真是...
  • PHP 多进程和多线程的优缺点

    千次阅读 2017-06-01 17:58:09
    多进程使用多进程, 子进程结束以后, 内核会负责回收资源使用多进程, 子进程异常退出不会导致整个进程Thread退出. 父进程还有机会重建流程.一个常驻主进程, 只负责任务分发, 逻辑更清楚.多进程方式更加稳定,另外利用...
  • 进程 一个进程,包括了代码、数据分配给进程的资源(内存)。在计算机系统里直观地说一个进程就是一个PID,操作系统保护进程...但如果该执行文件是基于多进程设计的话,操作系统会在最初的进程上创建出多个进程出来,
1 2 3 4 5 ... 20
收藏数 648,756
精华内容 259,502
关键字:

多进程和多线程区别