Go 语言原本 under the hood
Go 语言原本

如何阅读本书

有读者告诉笔者,这本书读起来吃力。学过、也用过几年 Go,碰到调度、窃取、内存这些章节, 仍然只能纸上谈兵。这个反馈是诚恳的,笔者也认。本书追求的深度,有一部分来自系统、并发、 编译这些领域几十年的积累,初次接触时坡度确实陡。但陡不意味着只能硬啃。一本书能提供的, 是几条让坡度变缓的路径。这一页就讲怎么走这几条路径。

第一件要说的事:本书不必从头读到尾。五大部分各自成篇,全景与历史语言特性并发内存编译器与工具链,彼此之间有引用,却不是必须顺序通关的关卡。 读者完全可以从最关心的那一部分切入,遇到不懂的前置概念再回头补。

按背景选一个起点

不同背景的读者,合适的入口并不相同。

  • 有 Go 使用经验、但没有系统底层基础(操作系统、体系结构、编译原理只是听说过)。 这是最常见、也最容易被劝退的一类读者。建议先把第一部分读完, 它从设计哲学讲到一个程序从启动到退出的生命周期,给后面的细节搭好一条主线; 之后挑一个自己用得最多的特性深入,比如 slice、map、channel,借熟悉的东西去摸陌生的机制。
  • 已经有操作系统或体系结构的底子。可以直接奔向最想读的部分。调度(第 9 章)、 内存分配与回收(第 1213 章)这类章节 会假设读者听得懂线程、缓存、虚拟内存这些词,对你不会构成障碍。
  • 带着一个具体问题来查阅。把书当手册用也是正当读法。每一节的标题都尽量自描述, 目录直接点到想要的机制即可,不必为了一节去通读整章。

术语第一次出现时正文会附上英文原词,若遇到不熟的概念, 书末的术语表收录了贯穿全书的核心词汇,可以随时回查。

难章的两遍读法

本书每一节大体按同一条线索铺开:先讲清要解决什么问题、核心直觉是什么, 再逐步下沉到理论、证明、跨系统的对比,最后落到裁剪过的源码。难,往往难在后半段。 对吃力的章节,笔者推荐分两遍读。

第一遍只读每节开头那几段,抓住「这个组件要解决什么问题、它的基本想法是什么」。 遇到形式化的证明(例如工作窃取的随机化分析与通信量界)、跨系统的横向对比 (Cilk、Java、Rust 各自的化身)、以及大段裁剪源码,先跳过去。一整章读下来, 心里先有一张骨架图:有哪些组件、它们怎么协作。

第二遍再回到跳过的地方,把证明、对比和实现细节填回骨架。这时它们不再是悬空的难点, 而是挂在一个已经理解的结构上的注脚,读起来会顺很多。纸上谈兵与真正读懂之间, 差的常常就是这第二遍。

几个高门槛主题的入口

读者反馈里点名最多的是三处:GMP 调度、工作窃取、内存分配。下面给出每一处的建议入口, 以及第一遍可以先放一放的部分。

goroutine 调度(GMP)。 不要直接从调度循环读起。先读 9.1 调度问题与 GMP 模型,弄清楚「要调度的是什么、 难在哪里」;再读 9.3 MPG 模型,它从「goroutine 到底是什么」 讲起,把 M、P、G 三个调度单元安顿好。这两节读透, 9.4 调度循环就只是「在这些单元之间搬运 G」, 不再吓人。若对进程与线程、用户态与内核态的分界还不清楚,先补这一点会省力很多。

工作窃取。 9.2 工作窃取式调度开篇就是一个具体问题: 每个 P 各有一条本地队列,活儿分布不均,怎么在不引入中心瓶颈的前提下把负载摊匀。 先把这个问题和「共享还是窃取」的取舍读懂,至于那条可证明的通信量界与随机化分析, 留到第二遍。理解窃取只需要一个前置概念:双端队列,一头进出供自己用,另一头供别人偷。

内存分配。12.1 设计原则12.2 组件入手,先建立 mcache、mcentral、mheap 这套多级结构的整体图景,再去看大对象、小对象、微对象三条分配路径有什么不同。 这一章会用到内存层级与缓存、虚拟内存与页的概念,手边不妨备一份操作系统教材的相关章节。

卡住了,欢迎说出来

哪一节、哪一段读起来吃力,往往不是读者的问题,而是那段引入还不够平缓。 把这些粗糙的入口逐一磨平,是本书持续修订的方向。读者若在某处卡住, 欢迎到 GitHub 仓库 指出具体的章节与段落,它会成为下一轮修订的起点。