并发和并行

2021/02/20 操作系统
原文地址:https://mp.weixin.qq.com/s/1beoHvvOBK-s_TXjTYWSYg

前言:并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。它们最关键的点就是:是否是『同时』。

概念理解

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。

你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。

你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

并发的关键是你有处理多个任务的能力,不一定要同时。

并行的关键是你有同时处理多个任务的能力。

所以我认为它们最关键的点就是:是否是『同时』。

图解一

Erlang 之父 Joe Armstrong用一张5岁小孩都能看懂的图解释了并发与并行的区别

image

并发:两队人交替使用咖啡机,如果前面的人有事先去厕所了,下一个人继续使用,不会等待这个人

并行:直接多了一个咖啡机,两队人使用两个咖啡机

此处的咖啡机可以看做CPU,等待去咖啡的人可以看作任务

图解二

任务描述

如图:

image

任务是将左边的一堆柴全部搬到右边烧掉,每个任务包括三个过程:取柴,运柴,放柴烧火。

这三个过程分别对应一个函数:

func get { geting }
func carry { carrying }
func unload { unloading }

串行模式

串行表示所有任务都一一按先后顺序进行。串行意味着必须先装完一车柴才能运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步骤,才能进行下一个步骤。

和稍后所解释的并行相对比,串行是一次只能取得一个任务,并执行这个任务。

假设这堆柴需要运送4次才能运完,那么当写下的代码类似于下面这种时,那么就是串行非并发的模式:

for(i=0;i<4;i++){
    get()
    carry()
    unload()
}

或者,将三个过程的代码全部集中到一个函数中也是如此:

func task {
    geting
    carrying
    unloading
}
for(i=0;i<4;i++){
    task()
}

这两种都是串行的代码模式。画图描述:

image

并行模式

image

并发

image

上图中将一个任务中的三个步骤取柴、运柴、卸柴划分成了独立的小任务,有取柴的老鼠,有运柴的老鼠,有卸柴烧火的老鼠。

如果上图中所有的老鼠都是同一只,那么是串行并发的,如果是不同的多只老鼠,那么是并行并发的。

并发程序的执行效率

我们思考一个问题:多线程就一定能提高处理速度吗?(多个goroutine的效率一定会高吗?)

CPU运行时,通过将于运行时间分片,通过调度来分配给各个进程线程来执行。因为时间片非常短,所以常常让人误以为是多个线程是同时并行执行。

使用多线程来提高程序处理速度,其本质是提高对CPU的利用率。主要是两个方面

  • 阻塞等待时充分利用CPU 当程序发生阻塞的操作时候,例如IO等待,CPU将就空闲下来了。而使用多线程,当一些线程发生阻塞的时候,另一些线程则仍能利用CPU,而不至于让CPU一直空闲。
  • 利用CPU的多核并行计算能力 现在的CPU基本上都是多核的。使用多线程,可以利用多核同时执行多个线程,而不至于单线程时一个核心满载,而其他核心空闲。

再次回到我们最初的问题:多线程就一定能提高处理速度吗?

显然不一定,我们都知道计算机程序基本上可以分为两类:

  • IO密集型程序
  • CPU密集型程序

当程序偏计算型的时候,盲目启动大量线程来并发,并不能提高处理速度,反而会降低处理速度。因为在多个线程进行切换执行的时候会带会一定的开销。其中有上下文切换开销CPU调度线程的开销线程创建和消亡的开销等。其中主要是上下文切换带来的开销

参考资料


Search

    公众号:豆仔gogo

    豆仔gogo

    Post Directory