即使经过多年的并发编程,开发人员也可能无法清楚地理解并发和并行之间的区别。在深入研究特定于 Go 的主题之前,首先必须让这些概念共享一个通用词汇表。我们将用一个现实生活中的例子来说明这整个部分:一家咖啡店。
在这家咖啡店里,有一个服务员负责接单并用一台咖啡机制作咖啡。顾客正在下订单,然后等待他们的咖啡:
如果服务员很难为所有顾客提供服务,但他希望加快整个流程,一个方法是拥有第二个服务员和第二台咖啡机。排队的顾客会等待一位服务员有空再下单:
在这个新流程中,系统的每个部分现在都是独立的。咖啡店应该以两倍的速度为消费者提供服务。这是咖啡店的并行实现。
如果我们想扩大规模,我们可以一遍又一遍地复制服务员和咖啡机。然而,这并不是唯一可能的咖啡店设计。事实上,另一种可能的设计可能是将服务员的工作分开,让一个负责接受订单,另一个负责制作咖啡。此外,我们可以为等待咖啡的顾客引入另一个队列(想想星巴克),而不是在为顾客服务之前阻塞顾客队列:
有了这个新设计,我们没有让事情变得平行。然而,受影响的是整体结构:我们将给定的角色分成两个角色,并引入了另一个队列。与并行性(即一次多次执行同一件事)不同,并发性与结构有关。
假设一个线程代表接受订单的服务员,另一个代表咖啡机,我们引入另一个线程来制作咖啡。每个线程都是独立的,但必须与其他线程协调。在这里,接受订单的服务员线程必须传达要准备哪种咖啡。同时,准备线程必须与咖啡机线程通信:
如果我们想增加吞吐量(例如,在一小时内为更多客户提供服务)怎么办?由于制作咖啡比接受订单要慢,因此可能的改变是聘请另一位服务员来制作咖啡:
在这里,结构保持不变。事实上,它仍然是一个三步设计:接受、准备、煮咖啡。因此,在并发方面没有变化。然而,我们又回到了添加并行性;这里有一个特定的步骤:准备咖啡。
现在,让我们假设减慢整个过程的部分是咖啡机。事实上,只有一台咖啡机会为准备线程引入一些争用,因为它们都在等待咖啡机线程可用。有什么解决办法?添加更多咖啡机线程:
我们通过引入更多机器来提高并行度,而不是一台咖啡机。同样,结构没有改变。它仍然是一个三步设计。然而,我们应该增加吞吐量,因为准备线程的争用级别应该已经降低。
通过这种设计,我们可以注意到一些重要的事情:并发可以实现并行。事实上,并发提供了一种结构来解决可能并行化的部分的问题。
并发是一次处理很多事情。
并行性是关于一次做很多事情。
-- Rob Pike
总之,并发和并行是不同的。并发是关于结构的,我们可以通过引入单独的并发线程可以处理的不同步骤来将顺序实现更改为并发实现。同时,并行性是关于执行的,我们可以通过添加更多并行线程来在步骤级别上利用它。理解这两个概念是成为一名熟练的 Go 开发人员的基础。
下一节将讨论一个普遍存在的错误:认为并发总是要走的路。