并发(concurrency)和并行(parallelism)在韩语和英语中的写法都很相似,因此经常被混用。
即使在 CPU 只有一个核心的时代,计算机也能一边播放音乐一边编辑文档。一个物理上一次只能做一件事的机器,究竟是如何“同时”完成多项工作的?
如果能回答这个问题,就已经分清这两个概念了。如果答不上来,本文正好会把这个关键点讲清楚。
Swift Concurrency、GCD(Grand Central Dispatch)的 concurrent queue 等名称也是如此。要准确理解它们,区分这两个概念是基础。
下面是核心总结。
- 并发:交替推进多个任务并“同时处理”它们的结构问题。属于逻辑概念
- 并行:多个任务在物理上同一时刻执行。属于硬件问题
- 单核也可以实现并发。并行则需要多个核心
- 并发通过代码结构实现,而并行是硬件利用该结构后的结果
用一台咖啡机理解
想象一家只有一名咖啡师(一个核心)的咖啡店。
来了两份订单。咖啡师在萃取 A 顾客的浓缩咖啡时加热 B 顾客的牛奶;萃取完成后做完 A 的饮品,再回到 B。两份订单都“同时进行中”,但无论截取哪个瞬间,咖啡师都只在做一件事。
这就是并发:把工作拆成小块,交替推进,同时处理多项任务。
再雇一名咖啡师(两个核心),两份订单就真的能在同一时刻制作。这就是并行。
关键在于:即使只有一名咖啡师,“交替处理订单的运行方式”依然成立;当咖啡师变成两名时,同一种方式就扩展为物理上的同时执行。
换成计算机来看
单核时代的多任务处理,正是一家只有一名咖啡师的咖啡店。
操作系统以几十毫秒为单位,在不同进程之间交替分配 CPU(时间片)。因此在人眼看来,音乐播放和文档编辑就像是同时运行的。
多核出现后,并行也加入了。现在 4 个核心可以真正同时执行 4 个线程。
我们可以这样整理它们的关系。
- 只有并行没有并发:将互不相关的任务分别交给各核心执行
- 没有并行的并发:单核时间片
- 并发 + 并行:现代计算的基础,由多个核心分担执行拆分后的任务
Go 语言的创造者 Rob Pike 有一场著名演讲,其标题用一句话概括了这种区别。
“Concurrency is not parallelism(并发不是并行)。”
他将并发区分为“处理多件事(dealing with)”,将并行区分为“做多件事(doing)”。
Swift Concurrency 这一名称的含义
从这个角度看,Apple 将 async/await 体系命名为 Swift “Concurrency”是很准确的。
async/await、Task、actor 都是用来表达“如何拆分任务、在哪里暂停、按什么顺序恢复”的工具。换句话说,它们是设计并发结构的语言机制。
这个结构实际会在几个核心上并行运行,则由运行时和系统调度器决定。开发者声明结构(并发),并行执行则交给系统负责。
async let a = fetchProfile() // 声明可以“同时推进”的结构
async let b = fetchFeed()
let result = try await (a, b) // 是否并行运行由系统决定
即使通过 TaskGroup 创建 100 个任务,如果只有 8 个核心,同一时刻最多也只能运行 8 个。其余任务会交替推进。在并发结构中,并行能力取决于硬件。
面试时用一句话说明
“并发是交替推进多个任务并共同处理它们的逻辑结构,而并行是多个任务在物理上同一时刻执行。因此,单核可以实现并发,但无法实现并行。”
也可以提前准备追问:“单核多任务是如何实现的?”(时间片),“并发代码是否总是并行运行?”(不是,由调度器决定)。
总结
- 并发是拆分工作、交替推进并共同处理多个任务的结构,属于逻辑层面的概念
- 并行是多个任务在物理上同一时刻执行,属于硬件层面的概念
- 单核也可以实现并发(时间片),但无法实现并行
- Rob Pike:并发是“处理”多件事,并行是“做”多件事
- Swift Concurrency 是声明并发结构的工具,实际的并行执行由系统决定
- 咖啡机类比:一名咖啡师交替制作代表并发,两名咖啡师一起制作代表并行

