很多开发者对并发(concurrency) 和并行( parallelism)混淆不清,这里我们试图在程序设计层面厘清两者的区别。

我们可以先记住,“并发”指的是程序的结构,“并行”指的是程序运行时的状态。

具体来说,所谓并行(parallelism),就是同时执行的意思,无需过度解读。判断程序是否处于并行的状态,就看同一时刻是否有超过一个“工作单位”在运行就好了。所以,单线程永远无法达到并行状态。

要达到并行状态,最简单的就是利用多线程和多进程。但是 Python 的多线程由于存在著名的 GIL(全局解释器锁),单个Python程序无法让两个线程真正“同时运行”,所以实际上是无法到达并行状态的。

对于并发(concurrency),我们应当了解并发指的是程序的“结构”。当我们说这个程序是并发的,实际上,这句话应当表述成“这个程序采用了支持并发的设计”。好,既然并发指的是人为设计的结构,那么怎样的程序结构才叫做支持并发的设计?

正确的并发设计的标准是:使多个操作可以在重叠的时间段内进行。

这句话的重点有两个。我们先看“(操作)在重叠的时间段内进行”这个概念。它是否就是我们前面说到的并行呢?并不完全相同。并行,当然是在重叠的时间段内执行,但是另外一种执行模式,也属于在重叠时间段内进行。这就是协程。

使用协程时,程序的执行看起来往往是这个样子:

mark

task1, task2 是两段不同的代码,比如两个函数(例如一个是处理行情的函数,一个是处理交易的函数),其中黑色块代表某段代码正在执行。注意,这里从始至终,在任何一个时间点上都只有一段代码在执行,但是,由于 task1 和 task2 在重叠的时间段内执行,所以这是一个支持并发的设计。与并行不同,单核单线程能支持并发。

mark

第二个重点是“可以在重叠的时间段内进行”中的“可以”两个字。“可以”的意思是,正确的并发设计使并发执行成为可能,但是程序在实际运行时却不一定会出现多个任务执行时间段 重叠的情形。比如:我们的程序会为每个任务开一个线程或者协程,只有一个任务时,显然不会出现多个任务执行时间段重叠的情况,有多个任务时,就会出现了。

这里我们看到,并发并不描述程序执行的状态,它描述的是一种设计,是程序的结构,比如上面例子里“为每个任务开一个线程”的设计。并发设计和程序实际执行情况没有直接关联,但是正确的并发设计让并发执行成为可能。反之,如果程序被设计为执行完一个任务再接着执行下一个,那就不是并发设计了,因为做不到并发执行。

那么,如何实现支持并发的设计?两个字:拆分。

之所以并发设计往往需要把流程拆开,是因为如果不拆分也就不可能在同一时间段进行多个任务了。这种拆分可以是平行的拆分,比如抽象成同类的任务,也可以是不平行的,比如分为多个步骤。

mark

并发和并行的关系

我们可以说,在程序设计层面,并发设计让并发执行成为可能,而并行是并发执行的一种模式。