Go 语言原本 Go 语言原本
English

如何阅读本书

不少读者反映本书读起来吃力:即便学过并使用过几年 Go,读到调度、工作窃取、内存管理这些章节时,仍难以真正读懂。本书追求的深度,有一部分来自系统、并发与编译等领域数十年的积累,初次接触时门槛确实较高。但这并不意味着只能从头到尾逐字攻读。本页介绍几种降低阅读难度的读法。

首先,本书不必从头读到尾。全书六个部分相对独立:全景与历史、语言特性的实现、并发、内存、编译器与工具链、异构计算与 AI 时代的 Go。各部分之间虽有交叉引用,却不必依次阅读。读者可以从最关心的部分读起,遇到不熟悉的前置概念再回头查阅。

按背景选择起点

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

  • 有 Go 使用经验,但缺少系统方面的基础,对操作系统、体系结构和编译原理只是略有耳闻。这是最常见的一类读者,也最容易半途而止。建议先读完第一部分:它从设计哲学讲到汇编与调用约定,再讲到一个程序从编译、启动到退出的全过程,为后续的细节建立一条主线。之后可以挑一个最常用的特性深入,例如 slice、map 或 channel,借熟悉的事物理解陌生的机制。
  • 已有操作系统或体系结构基础,可以直接阅读最感兴趣的部分。调度(第 9 章)、内存分配与垃圾回收(第 12 章、第 13 章)等章节默认读者了解线程、缓存、虚拟内存等概念,对这类读者不会构成障碍。
  • 带着具体问题查阅,可以把本书当作手册使用。各节标题尽量做到望文知义,从目录直接跳到所需的机制即可,不必为一节通读整章。

术语第一次出现时,正文会附上英文原文;遇到不熟悉的概念,可以随时查阅书末的术语表。

难章的两遍读法

本书各节大体沿同一条线索展开:先说明要解决什么问题、核心的直觉是什么,再逐步深入到理论、证明以及与其他系统的比较,最后落到裁剪过的源码。难点往往集中在后半部分。对于吃力的章节,建议分两遍阅读。

第一遍只读各节开头几段,把握这个组件要解决什么问题、基本思路是什么。遇到形式化的证明(例如工作窃取的随机化分析与通信量界限)、与其他系统的横向比较(例如 Cilk、Java 与 Tokio 中的工作窃取),以及大段的源码,可以先跳过。读完一章,心中先有一副骨架:有哪些组件,它们如何协作。

第二遍再回到跳过的部分,把证明、比较和实现细节填回骨架。此时它们不再是孤立的难点,而是附着在已经理解的结构之上的细节,读起来会顺畅许多。粗知其意与真正读懂之间,差的往往就是这第二遍。

几个难点主题的入口

读者反馈中提到最多的难点有三处:goroutine 调度、工作窃取和内存分配。下面给出各自的建议入口,以及第一遍可以暂时略过的部分。

goroutine 调度。 不宜直接从调度循环读起。先读 9.1 调度问题与 GMP 模型,弄清调度的对象是什么、难点在哪里;再读 9.3 MPG 模型与并发调度单元,它从 goroutine 在协程谱系中的位置讲起,说明 G、M、P 三种调度单元在运行时中如何表示。读懂这两节,9.4 调度循环讨论的便只是如何在这些单元之间安排 G 的执行。若对进程与线程、用户态与内核态的区别还不清楚,先补上这些概念会省力许多。

工作窃取。 9.2 工作窃取式调度从一个具体问题开始:每个 P 各有一条本地队列,负载难免不均,如何在不引入中心瓶颈的前提下平衡负载。第一遍读懂这个问题、工作共享与工作窃取的取舍,以及 Go 寻找可运行 G 的顺序即可,9.2.4 中的理论保证留到第二遍。经典的工作窃取以每个处理器一个双端队列为基础:所有者在一端存取任务,窃取者从另一端取走任务。Go 的本地队列与此有所不同,9.2.4 对此有专门的说明。

内存分配。 从 12.1 设计原则与 12.2 组件入手:先建立由 mcache、mcentral、mheap 组成的多级结构的整体图景,再看 mallocgc 如何按对象的大小以及是否含有指针,把请求分流到不同的分配路径(12.1.4)。这一章会用到存储层次与缓存、虚拟内存与页等概念,手边不妨备一本操作系统教材以供查阅。

欢迎反馈阅读中的困难

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