<!-- title: “并发不是并行”神课深读 -->
<!-- domain: programming -->

# “并发不是并行”：一场改变了百万程序员思维方式的演讲

## 概述：为什么这条视频值得你花30分钟

在编程世界里，很少有哪场演讲能像它一样，只用一个卡通地鼠搬书的比喻，就把困扰无数程序员十几年的概念混淆一次性讲清楚。Rob Pike 的《Concurrency Is Not Parallelism》（并发不是并行）自 2012 年初被上传到 Heroku 官方 YouTube 频道以来，长期是并发主题下被引用最多的演讲之一，播放量达百万级（据频道页面，演讲年代较早，精确数字以当前页面为准），在 Hacker News 和 Reddit 的 r/golang、r/programming 上至今仍被反复翻出讨论。

它值得深读的原因有三个：第一，它纠正的错误几乎人人都在犯——把“并发”当成“并行”的同义词，进而错误地优化程序；第二，它背后连着一条从 1978 年图灵奖得主 C. A. R. Hoare 的 CSP 论文一路延伸到 Go 语言运行时的完整理论谱系，是这个领域少有的“有根”的技术主张；第三，在 AI 大量生成代码的今天，理解“并发是一种程序结构方式，而不是提速手段”这件事，正在变得比以往任何时候都重要——因为 AI 能帮你写出并发形状的代码，却替你判断不了它是不是该并发。

## 视频基本信息

| 项目 | 内容 |
|------|------|
| 视频标题 | Concurrency Is Not Parallelism（并发不是并行） |
| 演讲者 | Rob Pike（贝尔实验室前研究员，UTF-8、Plan 9 参与设计者，Go 语言联合创始人） |
| 场合 | Heroku 举办的 Waza 开发者大会 |
| 频道 | Heroku 官方 YouTube 频道 |
| 发布时间 | 2012 年初（据频道页面） |
| 播放量 | 百万级（据频道页面，历史数据） |
| 时长 | 约 26 分钟 |
| 核心主张 | 并发是程序的结构设计问题，并行是程序的执行问题；两者相关但互不包含 |

先交代一下演讲者的分量。Rob Pike 在贝尔实验室参与了 Unix 系统的后期开发，是 UTF-8 编码方案的共同设计者（与 Ken Thompson），还主导了 Plan 9 操作系统和 Newsqueak、Alef、Limbo 等几门带通道（channel）概念的语言。2007 年起他在 Google 与 Thompson 等人共同设计了 Go 语言。也就是说，这场演讲讲的不是“我对并发的理解”，而是“我为什么把 Go 设计成这样”——演讲者本身就是这条技术路线的立法者。

## 核心主张拆解

### 一、两个词，两个世界：并发与并行的严格区分

演讲开篇，Pike 给出了全场最核心的一对定义，这两句话后来被无数幻灯片和教科书引用：

> **并发（Concurrency）：把一个程序组织成多个可独立执行的组成部分（composition of independently executing processes）。**
> **并行（Parallelism）：多个计算同时执行（simultaneous execution of possibly related computations）。**

注意这两个定义的落点完全不同：并发的关键词是“组织”（composition），说的是**你如何设计程序**；并行的关键词是“同时执行”，说的是**程序如何在硬件上跑起来**。并发属于设计层，并行属于执行层。

Pike 强调的关键推论是：并发与并行**不是对立关系，也不是包含关系，而是“相关但不同”**。一个并发设计的程序，可以在单核机器上以时间片轮转的方式交错运行——它依然是并发的，但从未并行；一个完全不加并发设计的程序，也可以靠多线程暴力堆硬件获得并行。演讲中那句被引用最多的话正是对这种关系的总结：“并发不是比并行更好或更糟，它只是不同。”

为什么这个区分重要？因为现实中的混乱都源于此：当你抱怨“程序慢”时，你面对的是并行问题，解法是多核、向量化、增加吞吐；当你抱怨“代码乱、难维护、回调地狱”时，你面对的是结构问题，解法是更好的程序组织方式。用并发手段去解决性能问题，就像用换个文件柜的方式指望办公室变亮——两件事可能都有用，但因果链完全不同。

### 二、地鼠搬书：一个比喻讲透“为什么并发是一种设计”

演讲最出名的部分，是 Pike 用几只卡通地鼠搬书的动画做的类比，这个比喻的层次感值得完整还原：

- **第一幕**：一只地鼠推一辆独轮车，从书堆往门口运书。书堆在左边，门口在右边，任务很简单。问题是效率低。
- **第二幕**：直观的提速办法是加地鼠。四只地鼠共用一辆车，结果它们互相等待、抢车，速度几乎没有提升——**瓶颈在车，不在地鼠**。这对应的是：在一个设计糟糕（强耦合）的程序上无脑加线程，得到的只是锁竞争而不是性能。
- **第三幕**：真正有效的改法是重新设计“任务分解”本身——比如把路径改成上下两条单向道（分解）；或者干脆不用车，改成地鼠组成人链把书传过去（改变通信方式）；或者每只地鼠负责自己那一段（任务划分）。**改动的是“结构”，提速是结构改对之后的副产品。**

这个比喻对应了并发设计的三个动作：**分解（把任务拆成可独立执行的部分）、通信（让各部分交换数据）、同步（让它们在正确的时机协作）**。Pike 的主张是：这三个动作做好了，程序的自然形状就是并发的，此时如果硬件给了多核，调度器就能把并发单元铺到多核上获得并行；如果没有多核，你依然赚到了清晰的代码结构。

这里有一个常被忽略的收尾强调：Pike 明确说，**地鼠搬书的类比不是在教你怎么优化搬运**，而是在说明“并发是更好的组织程序的方式”。观众如果把重点放在“多加地鼠还是多买车”上，就把一场设计演讲读成了性能调优演讲——这是这条视频传播史上最常见的一次误读。

### 三、理论溯源：从 Hoare 的 CSP 到 Go 的 channel

这场演讲真正硬核的部分，是它把 Go 的并发原语放到了一条长达三十多年的学术谱系上，而不是把它们当成 Go 的发明。

源头是英国计算机科学家 **Tony Hoare（C. A. R. Hoare，1980 年图灵奖得主）1978 年发表在《Communications of the ACM》上的论文《Communicating Sequential Processes》（通信顺序进程，简称 CSP）**。CSP 的核心思想在当时相当激进：与其让多个执行流通过共享内存、再加锁来协调，不如让它们**通过“通信”来同步**——两个进程之间立一条通道，发送方发出消息，接收方收下消息，这一收一发本身就是同步点。在无缓冲通道上，发送方会阻塞到接收方就绪，这被称为“会合”（rendezvous）。

CSP 的思想后来被多个工程系统验证：INMOS 公司为 Transputer 芯片设计的 **occam 语言**几乎逐字实现了 CSP 语义；CSP 的代数方法还发展成了形式化验证工具链，一个著名的应用是 1996 年 Gavin Lowe 用 CSP 模型检验发现了 Needham–Schroeder 认证协议的漏洞并给出修正版。这些都是“通过通信来同步”不是民科方案、而是经过数学和工业双重检验的证据。

Pike 本人则是这条谱系上的连环实践者：贝尔实验室的 **Newsqueak**（1980 年代末，通道成为一等公民）→ Plan 9 上的 **Alef** → 贝尔实验室 Inferno 系统的 **Limbo** → 最终到 Go。Go 把这套思想做了工程化裁剪：**goroutine** 是极轻量的执行单元（初始栈只有几 KB、按需增长，启动成本远低于操作系统线程），**channel** 是带类型的通信管道，**select** 语句允许一个 goroutine 同时等待多个通道事件。演讲中 Pike 用一句话总结了这套设计哲学，它后来刻进了 Go 的官方文档《Effective Go》：

> **不要通过共享内存来通信，而要通过通信来共享内存。**

### 四、运行时的魔法：为什么“轻量并发”在今天变得可行

地鼠和通道讲的是设计哲学，但这场演讲还有一个不那么显眼、却决定了“channel 这种东西到底实不实用”的技术支点：**Go 的运行时调度器**。

传统上，在程序里开十万个执行流是不可想象的——操作系统线程的栈以 MB 计，上下文切换动辄微秒级，十万个线程光内存就吃掉几十 GB。Go 的做法是 **M:N 调度**：把 M 个 goroutine 复用到 N 个操作系统线程上。goroutine 的切换由 Go 运行时在用户态完成，成本低几个数量级；某个 goroutine 阻塞在通道或网络读写上时，运行时会把同一线程上的其他 goroutine 挪走，而不是让操作系统线程干等。2012 年前后 Go 团队（Dmitry Vyukov 主导的调度器重设计）引入了 G-M-P 模型——G 是 goroutine，M 是操作系统线程，P 是逻辑处理器（调度上下文），并配合工作窃取（work stealing）来均衡负载。正是这套调度器让“以通信为主的并发结构”在工程上可以承载十万级并发单元，而不只是课堂上的玩具。

演讲里有个常被当作“彩蛋”的细节值得单独指出：**你把 Go 程序设成单核调度（例如 runtime.GOMAXPROCS(1)），程序里成百上千个 goroutine 依然会正确地交错运行、通信、同步，只是慢**。这个实验是“并发≠并行”最直接的活体证明——并发结构不依赖多核而存在，多核只是让它跑得更快。

### 五、划清边界：并发不是性能银弹

整场演讲最容易被曲解、也被 Pike 反复自我澄清的一点，是**并发与性能的关系**。他明确警告：把程序改成并发结构**不等于**它会变快。性能提升来自并行执行，而并行执行需要三件事凑齐：硬件有多个执行单元、调度器能把任务铺上去、任务的粒度合适（不会细到通信开销吃掉全部收益）。

这里连着一条更老的定律作为天花板：**Amdahl 定律**（Gene Amdahl，1967）。程序中无法并行的串行部分会硬性限制加速比——哪怕 95% 的代码都能并行，在无穷多核上最多也只能加速 20 倍。并发设计能帮你把“可并行的部分”干净地暴露出来，但它变不出串行部分所没有的速度。

## 知识与理论溯源：这张主张有真实的根

把视频里的关键主张逐条对回真实文献与工程史，这张知识图谱是这样的：

| 视频中的主张 | 真实来源 |
|------|------|
| 通过通信同步、会合式通道 | Hoare, *Communicating Sequential Processes*, CACM 1978；1985 年同名专著发展为进程代数 |
| goroutine/channel 的语言设计 | Newsqueak（Pike，1980s末）→ Alef（Plan 9）→ Limbo（Inferno）→ Go |
| M:N 调度、工作窃取 | Go 运行时 GMP 模型（Go 团队，Dmitry Vyukov 的可扩展调度器设计，2012） |
| 通道作为一等公民的语言实践 | occam 语言（INMOS，为 Transputer 设计，1980s） |
| CSP 形式化验证的实际价值 | Lowe 1996 用 CSP 模型检验发现并修复 Needham–Schroeder 协议漏洞 |
| 串行部分限制加速上限 | Amdahl, *Validity of the Single Processor Approach*, 1967 |
| “通信式”并发的另一条路径 | Erlang/OTP 的 Actor 模型（Joe Armstrong，2003 年博士论文系统阐述） |

值得注意的是 Erlang 这条对照线：Actor 模型与 CSP 同样主张“不共享、靠消息”，但 Actor 的邮箱是异步的、每个 Actor 有身份地址，而 Go 的无缓冲通道是同步会合的。Pike 在演讲中没有展开这组对比，但理解它之后你会发现 Go 的选择是刻意的：**同步通道让“收发即同步”成为语言语义的一部分，程序的正确性论证因此简单得多**——代价是某些高吞吐场景要自己加缓冲池。

## 批判性审查：哪些站得住，哪些要打折

**证据充分的部分。** “并发/并行”的概念区分在计算机科学内部没有争议，属于定义层面的澄清。CSP 的理论与工程传承是真实且有据可查的：从 occam 到 Go，从形式化验证到工业部署，这条线经得起最严格的溯源。Go 调度器的工程成绩也有大量生产环境背书——Docker、Kubernetes 这类以并发见长的基础设施软件都是 Go 写的，这至少证明“轻量并发单元 + 通道”在超大规模工程中可用。

**需要打折扣或常被误读的部分。**

第一，“并发结构让程序更易懂”是一个**有条件成立**的主张。演讲把它作为提案提出，但没有给出实证研究；四十多年来，并发程序的正确性难题（死锁、竞态、生命周期管理）从未消失，只是换了形状——通道用错了样样会泄漏：一个 goroutine 往无人接收的通道发送，它就永远卡在那里，这就是“goroutine 泄漏”。更诚实的读法是：**并发把“哪些代码可能同时跑”这个隐式问题，变成了通道两端显式的数据交接**——复杂度被转移和显式化了，而不是被消灭了。

第二，“用通信代替共享内存”不等于“Go 程序不该用锁”。Go 标准库保留了 sync.Mutex、sync.WaitGroup 和原子操作，官方文档的态度一直是“哪个更有表达力用哪个”。实践中大量高性能路径就是锁加共享结构；channel 最适合的场景是任务分发、结果汇聚、事件流，而不是所有数据传递。社区里的绝对化口号（“一切皆 channel”）是对演讲的二次扭曲。

第三，2012 年的演讲有时代局限。彼时 Go 1.0 尚未发布（2012 年 3 月才正式发布），此后运行时变化不小：1.14 引入了基于信号的**异步抢占**，解决了个别 goroutine 长时间占用调度导致饿死的问题；goroutine 栈管理、调度细节也持续演进。演讲中的思想框架依然成立，但涉及运行时行为的具体描述应以当前文档为准。

第四，公平地说，演讲对“并发设计更容易获得并行”的乐观，在 CPU 世界之外遭遇了竞争方案：Rust 的 async/await 走的是无栈协程加显式状态机路线（借助所有权系统在编译期杜绝数据竞态），C++20、Java 21（虚拟线程）也都给出了自己的轻量并发答案。**“轻量执行单元 + 显式数据交接”这个大方向赢了，但具体哪个语言形态胜出，至今仍是进行时。**

## 认知提升与实用启示

**第一条：给手头的代码做一次“C/P 测试”。** 下次你想写并发代码时，先强制自己回答一个问题：我要解决的是结构问题（回调嵌套、状态散落、事件交织）还是速度问题（单核吃满还嫌慢）？前者用并发的形状去重构，后者先上性能分析器找瓶颈。一个立竿见影的自检实验：把程序设成单核跑一遍（Go 里一行 `runtime.GOMAXPROCS(1)`），如果结果正确只是变慢，说明你的并发结构是健康的；如果结果直接错了，你写出的不是并发设计，而是裸奔的共享内存。

**第二条：用“工作池 + 通道”替换线程/回调的两难。** 大量所谓的并发难题，形状其实是同一个：一堆独立任务、有限的执行资源、需要收集结果。对应的通用模式是固定数量的 worker goroutine 从任务通道取活、干完写入结果通道、主流程用 sync.WaitGroup 或关闭通道来收尾。这个模式天然限定了并发度（不会开出十万个连接打爆对端），让“生产者—消费者”的边界在类型系统里可见。用它替换掉嵌套回调或裸线程池，代码的可读性差异立现。

**第三条：给数据指定唯一主人。** “通过通信来共享内存”落地成习惯就是一条纪律：每块可变数据在任意时刻只归一个执行流所有，要交接就走通道；确实必须共享的，用锁保护并开启竞态检测器（Go 的 `-race` 标志，源自 ThreadSanitizer 技术）跑一遍测试。这条纪律同样迁移到其他语言——Rust 的所有权系统把它做成了编译错误，Java 的虚拟线程场景下也依然适用。

## 视频论证结构图

```mermaid
graph TD
    A[问题：程序为什么乱且慢] --> B{区分两个概念}
    B --> C[并发：程序的结构设计<br/>把任务分解 + 通信 + 同步]
    B --> D[并行：程序的执行状态<br/>需要多核硬件支持]
    C --> E[理论根基<br/>Hoare CSP 1978<br/>通信即同步]
    C --> F[工程载体<br/>goroutine 轻量执行流<br/>channel 类型化通道]
    E --> G[谱系传承<br/>occam / Newsqueak / Limbo]
    F --> H[运行时支撑<br/>M:N 调度 GMP 模型<br/>用户态切换 工作窃取]
    H --> I{硬件有多个核?}
    I -->|有| J[调度器铺开任务<br/>获得并行加速]
    I -->|没有| K[时间片交错运行<br/>结构收益仍在]
    D --> L[天花板<br/>Amdahl 定律<br/>串行部分限制加速比]
    J --> M[结论：并发是更好的组织方式<br/>并行是其副产品]
    K --> M
    L --> M
```

## 延伸阅读

1. [Rob Pike: Concurrency Is Not Parallelism（YouTube 检索入口）](https://www.youtube.com/results?search_query=Rob+Pike+Concurrency+Is+Not+Parallelism) — 本文解读的演讲本体，Heroku 官方频道发布
2. [C. A. R. Hoare, *Communicating Sequential Processes*, CACM 1978](https://doi.org/10.1145/359576.359585) — 演讲全部思想的源头论文
3. [Effective Go: Concurrency 一章](https://go.dev/doc/effective_go#concurrency) — Go 官方对“通信即同步”的工程化阐述

## 参考来源

1. [Concurrency Is Not Parallelism — Rob Pike（YouTube 检索）](https://www.youtube.com/results?search_query=Rob+Pike+Concurrency+Is+Not+Parallelism) — 本文解读的核心视频（Heroku 官方频道，2012 年初上传，播放量据频道页面）
2. [Hoare, Communicating Sequential Processes, CACM 1978](https://doi.org/10.1145/359576.359585) — CSP 理论原始论文
3. [Effective Go — Concurrency](https://go.dev/doc/effective_go#concurrency) — “不要通过共享内存来通信”的官方出处
4. [The Go Blog: Share Memory By Communicating](https://go.dev/blog/codelab-share-memories) — Go 团队对通道交接模式的官方示例（据 Go 官方博客目录）

---
> 本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵**爱卫生**（微信ID: gprshome201101）维护，聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。
> 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
