[转载]并行与并发的区别
本文转载自https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/,仅做个人学习使用,侵权请联系删除
“并发” 指的是程序的结构,“并行” 指的是程序运行时的状态
并行(parallelism)
这个概念很好理解。所谓并行,就是同时执行的意思,无需过度解读。判断程序是否处于并行的状态,就看同一时刻是否有超过一个 “工作单位” 在运行就好了。所以,单线程永远无法达到并行状态。
要达到并行状态,最简单的就是利用多线程和多进程。但是 Python 的多线程由于存在著名的 GIL,无法让两个线程真正 “同时运行”,所以实际上是无法到达并行状态的。
并发(concurrency)
要理解 “并发” 这个概念,必须得清楚,并发指的是程序的 “结构”。当我们说这个程序是并发的,实际上,这句话应当表述成 “这个程序采用了支持并发的设计”。好,既然并发指的是人为设计的结构,那么怎样的程序结构才叫做支持并发的设计?
正确的并发设计的标准是:使多个操作可以在重叠的时间段内进行 (two tasks can start, run, and complete in overlapping time periods)。
这句话的重点有两个。我们先看 “(操作)在重叠的时间段内进行” 这个概念。它是否就是我们前面说到的并行呢?是,也不是。并行,当然是在重叠的时间段内执行,但是另外一种执行模式,也属于在重叠时间段内进行。这就是协程。
使用协程时,程序的执行看起来往往是这个样子:
task1, task2 是两段不同的代码,比如两个函数,其中黑色块代表某段代码正在执行。注意,这里从始至终,在任何一个时间点上都只有一段代码在执行,但是,由于 task1 和 task2 在重叠的时间段内执行,所以这是一个支持并发的设计。与并行不同,单核单线程能支持并发。
经常看到这样一个说法,叫做并发执行。现在我们可以正确理解它。有两种可能:
- 原本想说的是 “并行执行”,但是用错了词
- 指多个操作可以在重叠的时间段内进行,即,真的并行,或是类似上图那样的执行模式。
我的建议是尽可能不使用这个词,容易造成误会,尤其是对那些并发并行不分的人。但是读到这里的各位显然能正确区分,所以下面为了简便,将使用并发执行这个词。
第二个重点是 “可以在重叠的时间段内进行”中的 “可以” 两个字。“可以”的意思是,正确的并发设计使并发执行成为可能,但是程序在实际运行时却不一定会出现多个任务执行时间段 overlap 的情形。比如:我们的程序会为每个任务开一个线程或者协程,只有一个任务时,显然不会出现多个任务执行时间段重叠的情况,有多个任务时,就会出现了。这里我们看到,并发并不描述程序执行的状态,它描述的是一种设计,是程序的结构,比如上面例子里 “为每个任务开一个线程” 的设计。并发设计和程序实际执行情况没有直接关联,但是正确的并发设计让并发执行成为可能.反之,如果程序被设计为执行完一个任务再接着执行下一个,那就不是并发设计了,因为做不到并发执行。
那么,如何实现支持并发的设计?两个字:拆分。
之所以并发设计往往需要把流程拆开,是因为如果不拆分也就不可能在同一时间段进行多个任务了。这种拆分可以是平行的拆分,比如抽象成同类的任务,也可以是不平行的,比如分为多个步骤。
并发和并行的关系
Different concurrent designs enable different ways to parallelize.
这句话来自著名的 talk: Concurrency is not parallelism。它足够 concise,以至于不需要过多解释。但是仅仅引用别人的话总是不太好,所以我再用之前文字的总结来说明:并发设计让并发执行成为可能,而并行是并发执行的一种模式。
最关键的一点是,计算机在不同层次上都使用了并行技术。之前我讨论的实际上仅限于 Task-Level 这一层,在这一层上,并行无疑是并发的一个子集。但是并行并非并发的子集,因为在 Bit-Level 和 Instruction-Level 上的并行不属于并发——比如引文中举的 32 位计算机执行 32 位数加法的例子,同时处理 4 个字节显然是一种并行,但是它们都属于 32 位加法这一个任务,并不存在多个任务,也就根本没有并发。
所以,正确的说法是这样:
并行指物理上同时执行,并发指能够让多个任务在逻辑上交织执行的程序设计按照我现在的理解,并发针对的是 Task-Level 及更高层,并行则不限。这也是它们的区别。