1.1 编程语言的发展
要理解 Go 为何长成今天这样,为何如此克制、如此偏执于编译速度、如此看重并发,得先看它诞生时 面对的是怎样一片语言版图,以及它想替谁解决怎样的痛。Go 不是凭空设计的,它是一群写了一辈子 系统软件的人,对当时主流语言长期不满的一次回应。读懂这段背景,全书后面对调度器、内存模型、 泛型的种种取舍,才有了一个统一的出发点。
1.1.1 语言演化的几条主线
编程语言半个多世纪的演化,可以粗略看作几股力量的拉锯。沿着几个维度去看,每一门有生命力的 语言,都是在这些维度上做了一组特定取舍的产物,没有哪门语言能在所有维度上同时最优。
- 抽象层级:从汇编的「直接操纵机器」,到 C 的过程抽象,到 C++/Java 的对象抽象,到函数式 语言的高阶抽象。层级越高,表达越凝练,离机器越远,对性能与可预测性的掌控也越弱。
- 内存安全:从 C 的手工
malloc/free(强大但极易出错:悬垂指针、越界、泄漏),到带垃圾 回收的托管语言(消除一大类内存错误,代价是运行时开销与停顿),再到 Rust 的所有权(编译期 保证安全,代价是更陡的学习曲线)。 - 并发模型:从手工线程加锁(危险且难推理),到 CSP / Actor 等更高层的消息传递
(1.3),到
async/await的无栈协程。 - 类型系统:在「静态强类型的安全」与「动态类型的灵活」之间反复摆动;又在「名义类型」与 「结构化类型」(4.2)之间各有选择。
- 工程性:编译速度、构建模型、工具链、依赖管理。这一维度长期被语言设计者轻视,却恰是 大规模软件协作的命脉,也是 Go 着力最猛的地方。
把几门有代表性的语言摆在这张坐标上,差异一目了然:
| 语言 | 内存管理 | 并发 | 类型 | 编译/执行 | 设计取向 |
|---|---|---|---|---|---|
| C | 手工 | 线程 + 锁 | 静态、弱 | 编译为本地码 | 贴近机器,零成本抽象 |
| C++ | 手工 / RAII | 线程 + 锁 | 静态、强、复杂 | 编译慢、执行快 | 表达力极大化 |
| Java | GC | 线程 + 锁 / 虚拟线程 | 静态、名义、反射丰富 | JIT、运行时重 | 「一次编写,到处运行」 |
| Python | GC(引用计数) | GIL + 线程 / async | 动态 | 解释执行 | 开发效率优先 |
| Rust | 所有权 | 线程 + async | 静态、强、所有权 | 编译慢、执行快 | 安全且零成本 |
| Go | GC | goroutine(CSP) | 静态、结构化接口 | 编译极快、执行快 | 简单、工程友好 |
这张表不是为了排座次,没有「最好」的一行,只有「为不同目标做了不同取舍」的若干行。Go 在表里 的位置,带 GC、CSP 并发、结构化接口、编译极快、设计取向是「简单」,正是它要解决的那些痛 逼出来的。
1.1.2 Go 诞生时的不满
Go 始于 2007 年的 Google 内部,由 Robert Griesemer、Rob Pike、Ken Thompson 三人发起。这几位的 履历本身就是 Go 的基因:Thompson 是 Unix 与 C 的共同缔造者,Pike 来自贝尔实验室、是 Plan 9 与 UTF-8 的设计者之一,Griesemer 则有 V8 与 HotSpot 的编译器背景。一门由「Unix 一脉的系统程序员 加编译器专家」设计的语言,天然会偏向简单、贴近系统、且极重工具链。
促使他们动手的,是大规模 C++ / Java 服务端开发里几桩具体而日常的痛苦。Rob Pike 在 《Go at Google》中讲得很直白,Go 是「为了解决 Google 的软件工程问题」而生,而非追求语言学上的 新颖。这些痛包括:
- 编译慢到拖垮节奏。 Google 庞大的 C++ 代码库,因头文件的传递性依赖,一个
.cc文件#include一个头,那个头又 include 别的头,层层展开,一次编译可能要把同一批头文件解析成千 上万遍,构建动辄数十分钟。开发的「改一行、编译、看结果」循环被严重拖累。Go 后来对编译速度 近乎偏执的重视(3.2、15),根子 就在这里。 - 依赖管理混乱。
#include的文本包含模型让依赖关系不清不楚、且重复编译。Go 的包模型 (包为最小单位、禁止循环依赖、只读依赖的导出摘要而非全部源码)与「未使用的导入即编译错误」, 正是冲着这个痛去的。 - 并发难写又危险。 C++ 的并发要靠线程库加手工锁,既啰嗦又极易出错(竞争、死锁),而
Google 的服务端程序本质上高度并发。Go 把并发做成了语言的一等公民,一个
go关键字、 channel 与select(1.3、9),让并发从「专家才 敢碰」变成「人人能写」。 - 语言过于庞杂。 C++ 特性繁多、相互交织,以至于一个团队里,对「该用语言的哪个子集」都难有 共识,新人要花很久才敢碰某些角落。Pike 提到,正是读 C++0x 草案的经历,坚定了他做一门 「小」语言的决心。
1.1.3 一种「少即是多」的回应
Go 的回应,是一种刻意的克制。它选择了:编译为本地机器码、带垃圾回收、静态类型;一套极简的 语法(仅 25 个关键字,少到能一口气背完);以 CSP(1.3)为蓝本的并发;以组合而非 继承为骨架的类型系统(4.2);以及对工具链与构建速度的 极度重视。
它有意不要的东西,和它要的东西同样醒目,且每一项「不要」都对应着上面某个痛或某条价值:
- 很长时间没有泛型(直到 2022 的 Go 1.18,8),因为没找到 一个不牺牲编译速度与简单性的方案,宁可不做也不草率。
- 没有异常(7),错误是普通的值,显式返回、显式处理,拒绝 「隐式的控制流跳转」。
- 没有继承,用接口与组合替代,避免类层级的脆弱与纠缠。
- 没有运算符重载、没有隐式类型转换,拒绝「看一行代码猜不出它在干什么」的魔法。
这种「用减法做设计」的取向,是贯穿全书的主线。一个浓缩的例子是 Russ Cox 2009 年提出的 「泛型的两难」(the generic dilemma):在「慢的程序员、慢的编译器、慢的运行时」之间,任何 泛型方案似乎只能三选其二。正因为 Go 极度看重后两者(编译快、运行时简洁),它宁可让程序员 暂时多写点重复代码,也迟迟不引入会牺牲它们的泛型,直到 2022 年找到一个能较好平衡三者的实现 (8.1)才出手。这十三年的等待,是 Go 设计哲学最鲜明的 注脚:复杂度必须挣得它的位置,新特性要先证明自己值得。
理解了 Go 诞生时的这些不满与回应,本书后面对每一处实现的剖析,为什么调度器是协作加信号抢占 (9.7)、为什么内存模型只给顺序一致原子 (11.9)、为什么 GC 死磕低延迟 (13),就都有了一个共同的坐标原点:它们都是「简单、工程友好、为 真实的大规模软件协作服务」这组价值,在各个角落的具体展开。
延伸阅读的文献
- Rob Pike. Go at Google: Language Design in the Service of Software Engineering. 2012. https://go.dev/talks/2012/splash.article (Go 的工程动机,最权威的一手论述)
- Russ Cox. The Generic Dilemma. 2009. https://research.swtch.com/generic
- The Go Authors. Frequently Asked Questions (FAQ): Origins / Design. https://go.dev/doc/faq
- Rob Pike. Less is exponentially more. 2012. https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html
- Brian W. Kernighan. Unix: A History and a Memoir. 2019 (Go 设计者的 Unix / Plan 9 渊源).
- Alan A. A. Donovan, Brian W. Kernighan. The Go Programming Language. 2015.
许可
© 2018-2026 The golang.design Initiative Authors. Licensed under CC-BY-NC-ND 4.0.