<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Go 语言原本</title>
    <link>/</link>
    <description>Recent content on Go 语言原本</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <atom:link href="/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>引言</title>
      <link>/zh-cn/preface/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/preface/</guid>
      <description>&lt;h1 id=&#34;引言&#34;&gt;引言&lt;/h1&gt;&#xA;&lt;p&gt;Go 语言从 2009 年诞生之初已有十余年的历史。&#xA;纵观大多数编程语言的历史进程，令人惊讶的是 Go 语言自身在进化的这十余年间，&#xA;语言本身并未发生太大变化，Go 语言的用户能够持续不断写出向后兼容的应用。&#xA;从语言设计的角度而言，作为一门从诞生之初就考虑低成本、高并发、简洁等原则的语言，&#xA;很难让人不对其简洁设计背后的各项实现机制以及具体工作原理所好奇。&#xA;本书就是一本讨论 Go 语言源码工程中的技术原理及其演进历程的书籍。&lt;/p&gt;&#xA;&lt;h2 id=&#34;致读者的话&#34;&gt;致读者的话&lt;/h2&gt;&#xA;&lt;p&gt;读者可能会好奇，设计总在演进、源码总在变化，为什么要耗费力气研究实际工作中可能永远不会接触的源码？&#xA;笔者以为不然，因为『软件工程发生在代码被非原作者阅读之时』，在阅读源码的过程中，&#xA;我们除了能进一步加深对语言本身的理解，更重要的则是理解某个设计背后所使用的根本原理，&#xA;以及当其他人在实现这个设计的过程中发生的工程决策、实践与实现技巧。&#xA;代码总是可以推倒重来，但原理却能『永生』。&lt;/p&gt;&#xA;&lt;p&gt;本书的创作愿景是涵盖整个 Go 语言的方方面面。这包括用户代码能直接接触的 Go 运行时组件、&#xA;与关键语言特性强相关的工具链、诸多重要的标准库等等。在部分情况下，&#xA;本书会讨论不同平台下的实现差异，但着重以 Linux amd64 为主。&lt;/p&gt;&#xA;&lt;h2 id=&#34;阅读的预备知识&#34;&gt;阅读的预备知识&lt;/h2&gt;&#xA;&lt;p&gt;阅读本书的读者应该具备一些基本的计算机科学素养，至少学过一门&lt;strong&gt;程序设计&lt;/strong&gt;课程以及&lt;strong&gt;数据结构&lt;/strong&gt;的课程，例如能够熟悉地谈论散列表、红黑树等概念。若你具备基本的&lt;strong&gt;离散数学&lt;/strong&gt;和&lt;strong&gt;概率论&lt;/strong&gt;知识，对谓词、随机变量等数学概念具有一定程度的理解，那么将会对阅读本书的部分章节有所帮助。&lt;/p&gt;&#xA;&lt;p&gt;本书的参考文献分为两种不同的类型。在每个章节后都附有进一步阅读的参考文献，&#xA;这类文献的主要目的是将读者指向书中尚未进一步深入探讨的话题，通常这类话题已经超出全书的讨论范畴，&#xA;这类文献给对该内容感兴趣的读者一个进一步阅读的空间；&#xA;其二是书后的参考文献，这些文献是在编写本书时引用、参考的主要文献，读者可以根据自己的需求选择并查阅本书参考过的文献。&lt;/p&gt;&#xA;&lt;p&gt;本书并未要求读者已经掌握使用 Go 语言，因此会在开篇快速介绍 Go 的语言规范。如果你已经具备 Go 语言编码和相关开发经验，则会对阅读本书有所帮助。&lt;/p&gt;&#xA;&lt;p&gt;若你担心本书的门槛，或读到调度、窃取、内存这类章节时感到吃力，&lt;a href=&#34;.././how-to-read&#34;&gt;如何阅读本书&lt;/a&gt;给出了按背景选择起点、以及难章分两遍读的具体建议。&lt;/p&gt;&#xA;&lt;h2 id=&#34;关于本书的版本&#34;&gt;关于本书的版本&lt;/h2&gt;&#xA;&lt;p&gt;本书的内容以 &lt;strong&gt;Go 1.26&lt;/strong&gt; 为基准做过一次系统的重写与校订，覆盖全景与历史、语言特性、并发、&#xA;内存、编译器与工具链、以及异构计算与 AI 六大部分（最后一部分还会触及 Go 1.27 的若干前沿特性）。&#xA;每一节都尽量回到「问题、设计、演化、取舍、实现」这条线索去&#xA;展开，并对照 Go 源码与一手设计文档加以核实。&lt;/p&gt;&#xA;&lt;p&gt;即便如此，源码仍在持续演进，书中也难免存在疏漏或随版本而过时之处。如果读者在阅读过程中&#xA;怀疑某段内容有误，欢迎在 &lt;a href=&#34;https://github.com/golang-design/under-the-hood/issues&#34;&gt;GitHub 仓库&lt;/a&gt;&#xA;提出 Issue 或 Pull Request，我们会持续修订。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何阅读本书</title>
      <link>/zh-cn/how-to-read/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/how-to-read/</guid>
      <description>&lt;h1 id=&#34;如何阅读本书&#34;&gt;如何阅读本书&lt;/h1&gt;&#xA;&lt;p&gt;有读者告诉笔者，这本书读起来吃力。学过、也用过几年 Go，碰到调度、窃取、内存这些章节，&#xA;仍然只能纸上谈兵。这个反馈是诚恳的，笔者也认。本书追求的深度，有一部分来自系统、并发、&#xA;编译这些领域几十年的积累，初次接触时坡度确实陡。但陡不意味着只能硬啃。一本书能提供的，&#xA;是几条让坡度变缓的路径。这一页就讲怎么走这几条路径。&lt;/p&gt;&#xA;&lt;p&gt;第一件要说的事：本书不必从头读到尾。六大部分各自成篇，&lt;a href=&#34;/zh-cn/part1overview/&#34;&gt;全景与历史&lt;/a&gt;、&#xA;&lt;a href=&#34;/zh-cn/part2lang/&#34;&gt;语言特性&lt;/a&gt;、&lt;a href=&#34;/zh-cn/part3concurrency/&#34;&gt;并发&lt;/a&gt;、&lt;a href=&#34;/zh-cn/part4memory/&#34;&gt;内存&lt;/a&gt;、&#xA;&lt;a href=&#34;/zh-cn/part5toolchain/&#34;&gt;编译器与工具链&lt;/a&gt;、&lt;a href=&#34;/zh-cn/part6hetero/&#34;&gt;异构计算与 AI&lt;/a&gt;，彼此之间有引用，却不是必须顺序通关的关卡。&#xA;读者完全可以从最关心的那一部分切入，遇到不懂的前置概念再回头补。&lt;/p&gt;&#xA;&lt;h2 id=&#34;按背景选一个起点&#34;&gt;按背景选一个起点&lt;/h2&gt;&#xA;&lt;p&gt;不同背景的读者，合适的入口并不相同。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;有 Go 使用经验、但没有系统底层基础（操作系统、体系结构、编译原理只是听说过）。&#xA;这是最常见、也最容易被劝退的一类读者。建议先把&lt;a href=&#34;/zh-cn/part1overview/&#34;&gt;第一部分&lt;/a&gt;读完，&#xA;它从设计哲学讲到一个程序从启动到退出的生命周期，给后面的细节搭好一条主线；&#xA;之后挑一个自己用得最多的特性深入，比如 slice、map、channel，借熟悉的东西去摸陌生的机制。&lt;/li&gt;&#xA;&lt;li&gt;已经有操作系统或体系结构的底子。可以直接奔向最想读的部分。调度（&lt;a href=&#34;/zh-cn/part3concurrency/ch09sched/&#34;&gt;第 9 章&lt;/a&gt;）、&#xA;内存分配与回收（&lt;a href=&#34;/zh-cn/part4memory/ch12alloc/&#34;&gt;第 12&lt;/a&gt;、&lt;a href=&#34;/zh-cn/part4memory/ch13gc/&#34;&gt;13 章&lt;/a&gt;）这类章节&#xA;会假设读者听得懂线程、缓存、虚拟内存这些词，对你不会构成障碍。&lt;/li&gt;&#xA;&lt;li&gt;带着一个具体问题来查阅。把书当手册用也是正当读法。每一节的标题都尽量自描述，&#xA;目录直接点到想要的机制即可，不必为了一节去通读整章。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;术语第一次出现时正文会附上英文原词，若遇到不熟的概念，&#xA;书末的&lt;a href=&#34;/zh-cn/glossary/&#34;&gt;术语表&lt;/a&gt;收录了贯穿全书的核心词汇，可以随时回查。&lt;/p&gt;&#xA;&lt;h2 id=&#34;难章的两遍读法&#34;&gt;难章的两遍读法&lt;/h2&gt;&#xA;&lt;p&gt;本书每一节大体按同一条线索铺开：先讲清要解决什么问题、核心直觉是什么，&#xA;再逐步下沉到理论、证明、跨系统的对比，最后落到裁剪过的源码。难，往往难在后半段。&#xA;对吃力的章节，笔者推荐分两遍读。&lt;/p&gt;&#xA;&lt;p&gt;第一遍只读每节开头那几段，抓住「这个组件要解决什么问题、它的基本想法是什么」。&#xA;遇到形式化的证明（例如工作窃取的随机化分析与通信量界）、跨系统的横向对比&#xA;（Cilk、Java、Rust 各自的化身）、以及大段裁剪源码，先跳过去。一整章读下来，&#xA;心里先有一张骨架图：有哪些组件、它们怎么协作。&lt;/p&gt;&#xA;&lt;p&gt;第二遍再回到跳过的地方，把证明、对比和实现细节填回骨架。这时它们不再是悬空的难点，&#xA;而是挂在一个已经理解的结构上的注脚，读起来会顺很多。纸上谈兵与真正读懂之间，&#xA;差的常常就是这第二遍。&lt;/p&gt;&#xA;&lt;h2 id=&#34;几个高门槛主题的入口&#34;&gt;几个高门槛主题的入口&lt;/h2&gt;&#xA;&lt;p&gt;读者反馈里点名最多的是三处：GMP 调度、工作窃取、内存分配。下面给出每一处的建议入口，&#xA;以及第一遍可以先放一放的部分。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;goroutine 调度（GMP）。&lt;/strong&gt; 不要直接从调度循环读起。先读&#xA;&lt;a href=&#34;.././part3concurrency/ch09sched/model&#34;&gt;9.1 调度问题与 GMP 模型&lt;/a&gt;，弄清楚「要调度的是什么、&#xA;难在哪里」；再读 &lt;a href=&#34;.././part3concurrency/ch09sched/mpg&#34;&gt;9.3 MPG 模型&lt;/a&gt;，它从「goroutine 到底是什么」&#xA;讲起，把 M、P、G 三个调度单元安顿好。这两节读透，&#xA;&lt;a href=&#34;.././part3concurrency/ch09sched/schedule&#34;&gt;9.4 调度循环&lt;/a&gt;就只是「在这些单元之间搬运 G」，&#xA;不再吓人。若对进程与线程、用户态与内核态的分界还不清楚，先补这一点会省力很多。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;工作窃取。&lt;/strong&gt; &lt;a href=&#34;.././part3concurrency/ch09sched/steal&#34;&gt;9.2 工作窃取式调度&lt;/a&gt;开篇就是一个具体问题：&#xA;每个 P 各有一条本地队列，活儿分布不均，怎么在不引入中心瓶颈的前提下把负载摊匀。&#xA;先把这个问题和「共享还是窃取」的取舍读懂，至于那条可证明的通信量界与随机化分析，&#xA;留到第二遍。理解窃取只需要一个前置概念：双端队列，一头进出供自己用，另一头供别人偷。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;内存分配。&lt;/strong&gt; 从 &lt;a href=&#34;.././part4memory/ch12alloc/basic&#34;&gt;12.1 设计原则&lt;/a&gt;与&#xA;&lt;a href=&#34;.././part4memory/ch12alloc/component&#34;&gt;12.2 组件&lt;/a&gt;入手，先建立 mcache、mcentral、mheap&#xA;这套多级结构的整体图景，再去看大对象、小对象、微对象三条分配路径有什么不同。&#xA;这一章会用到内存层级与缓存、虚拟内存与页的概念，手边不妨备一份操作系统教材的相关章节。&lt;/p&gt;</description>
    </item>
    <item>
      <title>1.1 编程语言的发展</title>
      <link>/zh-cn/part1overview/ch01intro/history/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch01intro/history/</guid>
      <description>&lt;h1 id=&#34;11-编程语言的发展&#34;&gt;1.1 编程语言的发展&lt;/h1&gt;&#xA;&lt;p&gt;要理解 Go 为何长成今天这样，为何如此克制、如此偏执于编译速度、如此看重并发，得先看它诞生时&#xA;面对的是怎样一片语言版图，以及它想替谁解决怎样的痛。Go 不是凭空设计的，它是一群写了一辈子&#xA;系统软件的人，对当时主流语言长期不满的一次回应。读懂这段背景，全书后面对调度器、内存模型、&#xA;泛型的种种取舍，才有了一个统一的出发点。&lt;/p&gt;&#xA;&lt;h2 id=&#34;111-语言演化的几条主线&#34;&gt;1.1.1 语言演化的几条主线&lt;/h2&gt;&#xA;&lt;p&gt;编程语言半个多世纪的演化，不是一条直线，而是几股力量的反复拉锯。把它压缩成一条时间线：&#xA;1950 年代末，Fortran 让科学计算摆脱汇编、Lisp 带来高阶函数与自动内存管理；1960 年代 Algol 确立&#xA;了结构化的块与语法；1970 年代 C 与 Unix 一同诞生，给了系统程序员一种「贴着机器又不必写汇编」的&#xA;语言；1980、90 年代 C++ 与 Java 把面向对象推向主流，前者要零成本、后者要可移植与托管；同期&#xA;Python、Perl 等脚本语言以开发效率见长；2000 年代后，Go 与 Rust 代表了「重新审视系统编程」的&#xA;两种回答，一个赌简单与并发，一个赌编译期的内存安全。&lt;/p&gt;&#xA;&lt;p&gt;每一步背后，是在几个维度上做了一组&lt;strong&gt;特定取舍&lt;/strong&gt;。没有哪门语言能在所有维度上同时最优，看清这几个&#xA;维度，才看得懂各家为何长成那样：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;抽象层级&lt;/strong&gt;：从汇编的「直接操纵机器」，到 C 的过程抽象，到 C++/Java 的对象抽象，到函数式&#xA;语言的高阶抽象。层级越高，表达越凝练，离机器越远，对性能与可预测性的掌控也越弱。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;内存安全&lt;/strong&gt;：从 C 的手工 &lt;code&gt;malloc&lt;/code&gt;/&lt;code&gt;free&lt;/code&gt;（强大但极易出错，悬垂指针、越界、泄漏），到带垃圾&#xA;回收的托管语言（消除一大类内存错误，代价是运行时开销与停顿），再到 Rust 的所有权（编译期&#xA;保证安全，代价是更陡的学习曲线）。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;并发模型&lt;/strong&gt;：从手工线程加锁（危险且难推理），到 CSP / Actor 等更高层的消息传递&#xA;（&lt;a href=&#34;.././csp&#34;&gt;1.3&lt;/a&gt;），到 &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; 的无栈协程。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;类型系统&lt;/strong&gt;：在「静态强类型的安全」与「动态类型的灵活」之间反复摆动；又在「名义类型」与&#xA;「结构化类型」（&lt;a href=&#34;../../../part2lang/ch04type/interface&#34;&gt;4.2&lt;/a&gt;）之间各有选择。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;工程性&lt;/strong&gt;：编译速度、构建模型、工具链、依赖管理。这一维度长期被语言设计者轻视，却恰是&#xA;大规模软件协作的命脉，也是 Go 着力最猛的地方。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;把几门有代表性的语言摆在这张坐标上，差异一目了然：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;语言&lt;/th&gt;&#xA;          &lt;th&gt;内存管理&lt;/th&gt;&#xA;          &lt;th&gt;并发&lt;/th&gt;&#xA;          &lt;th&gt;类型&lt;/th&gt;&#xA;          &lt;th&gt;编译/执行&lt;/th&gt;&#xA;          &lt;th&gt;设计取向&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;C&lt;/td&gt;&#xA;          &lt;td&gt;手工&lt;/td&gt;&#xA;          &lt;td&gt;线程 + 锁&lt;/td&gt;&#xA;          &lt;td&gt;静态、弱&lt;/td&gt;&#xA;          &lt;td&gt;编译为本地码&lt;/td&gt;&#xA;          &lt;td&gt;贴近机器，零成本抽象&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;C++&lt;/td&gt;&#xA;          &lt;td&gt;手工 / RAII&lt;/td&gt;&#xA;          &lt;td&gt;线程 + 锁&lt;/td&gt;&#xA;          &lt;td&gt;静态、强、复杂&lt;/td&gt;&#xA;          &lt;td&gt;编译慢、执行快&lt;/td&gt;&#xA;          &lt;td&gt;表达力极大化&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Java&lt;/td&gt;&#xA;          &lt;td&gt;GC&lt;/td&gt;&#xA;          &lt;td&gt;线程 + 锁 / 虚拟线程&lt;/td&gt;&#xA;          &lt;td&gt;静态、名义、反射丰富&lt;/td&gt;&#xA;          &lt;td&gt;JIT、运行时重&lt;/td&gt;&#xA;          &lt;td&gt;「一次编写，到处运行」&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Python&lt;/td&gt;&#xA;          &lt;td&gt;GC（引用计数）&lt;/td&gt;&#xA;          &lt;td&gt;GIL + 线程 / async&lt;/td&gt;&#xA;          &lt;td&gt;动态&lt;/td&gt;&#xA;          &lt;td&gt;解释执行&lt;/td&gt;&#xA;          &lt;td&gt;开发效率优先&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Rust&lt;/td&gt;&#xA;          &lt;td&gt;所有权&lt;/td&gt;&#xA;          &lt;td&gt;线程 + async&lt;/td&gt;&#xA;          &lt;td&gt;静态、强、所有权&lt;/td&gt;&#xA;          &lt;td&gt;编译慢、执行快&lt;/td&gt;&#xA;          &lt;td&gt;安全且零成本&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Go&lt;/td&gt;&#xA;          &lt;td&gt;GC&lt;/td&gt;&#xA;          &lt;td&gt;goroutine（CSP）&lt;/td&gt;&#xA;          &lt;td&gt;静态、结构化接口&lt;/td&gt;&#xA;          &lt;td&gt;编译极快、执行快&lt;/td&gt;&#xA;          &lt;td&gt;简单、工程友好&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;这张表不是为了排座次，没有「最好」的一行，只有「为不同目标做了不同取舍」的若干行。Go 在表里&#xA;的位置，带 GC、CSP 并发、结构化接口、编译极快、设计取向是「简单」，正是它要解决的那些痛&#xA;逼出来的。&lt;/p&gt;</description>
    </item>
    <item>
      <title>1.2 Go 语言综述</title>
      <link>/zh-cn/part1overview/ch01intro/go/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch01intro/go/</guid>
      <description>&lt;h1 id=&#34;12-go-语言综述&#34;&gt;1.2 Go 语言综述&lt;/h1&gt;&#xA;&lt;p&gt;这一节为全书搭一个骨架。它不是语法手册，Go 的语法在任何入门书里都讲得清楚，重复它没有意义。&#xA;我们要做的，是先建立一个&lt;strong&gt;俯瞰全局的视角&lt;/strong&gt;：Go 由哪几层构成、它有哪些&lt;strong&gt;与众不同&lt;/strong&gt;的设计决定、&#xA;以及这些决定分别在本书的哪一章展开。读完这一节，再钻进后面任何一章，都不会迷路。&lt;/p&gt;&#xA;&lt;h2 id=&#34;121-三层视角与厚重的运行时&#34;&gt;1.2.1 三层视角与厚重的运行时&lt;/h2&gt;&#xA;&lt;p&gt;理解 Go，可以分三层来看，本书也大致按这三层加上历史铺陈来组织。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;语言层&lt;/strong&gt;，你直接书写的东西：语法、类型系统、并发原语、错误处理。它刻意小而正交，25 个关键字&#xA;就是全部。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;运行时层&lt;/strong&gt;，与你的程序一起被编译进同一个二进制、在背后默默支撑的「小操作系统」：goroutine&#xA;调度器、内存分配器、垃圾回收器、栈管理。这是 Go 最厚重、也最值得剖析的部分。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;工具链层&lt;/strong&gt;，把源码变成可运行、可诊断、可维护程序的机器：编译器、链接器、模块系统、测试与&#xA;可观测性工具。&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart TD&#xA;    SRC[&amp;#34;你的 Go 源码&amp;#34;] --&amp;gt;|&amp;#34;编译器 / 链接器&amp;lt;br/&amp;gt;（工具链层，第 3、15、16、17 章）&amp;#34;| BIN&#xA;    BIN[&amp;#34;单个自包含的二进制&amp;lt;br/&amp;gt;内含 runtime（运行时层，第 9–14 章）&amp;#34;] --&amp;gt;|&amp;#34;运行时：调度 goroutine、分配内存、回收垃圾、管理栈&amp;#34;| RUN&#xA;    RUN[&amp;#34;在操作系统线程上并发执行&amp;lt;br/&amp;gt;（语言层语义，第 4–8、10、11 章）&amp;#34;]&#xA;    style BIN fill:#e8f0fe&lt;/pre&gt;&#xA;&lt;p&gt;Go 的一个鲜明特征，是它的&lt;strong&gt;运行时层异常厚重&lt;/strong&gt;。一门 C 程序几乎不带运行时，一门 Java 程序把&#xA;运行时放进一个独立的虚拟机；Go 走的是第三条路，它把调度、垃圾回收、栈管理这些本属操作系统&#xA;范畴的复杂度，&lt;strong&gt;接管进与你的代码一同编译、一同链接的运行时里&lt;/strong&gt;，换取上层的简洁：一个 &lt;code&gt;go&lt;/code&gt;&#xA;关键字就能起一个并发执行、自动内存管理、可在任意深度阻塞的 goroutine。这就是为什么一个 Go&#xA;二进制即便 &lt;code&gt;FROM scratch&lt;/code&gt; 也能独立运行，它自带了一个微型操作系统（这条「自带运行时」的线，&#xA;从 &lt;a href=&#34;../../ch03life/boot&#34;&gt;3.5 启动引导&lt;/a&gt; 一直贯穿到 &lt;a href=&#34;/zh-cn/part4memory/ch14stack/&#34;&gt;14 执行栈&lt;/a&gt;）。&#xA;代价是二进制更大、运行时本身的复杂度可观，但收益是部署极简、并发与内存管理对用户透明。本书&#xA;大半篇幅，正是在剖析这个「藏在二进制里的微型操作系统」。&lt;/p&gt;&#xA;&lt;h2 id=&#34;122-几个与众不同的设计决定&#34;&gt;1.2.2 几个与众不同的设计决定&lt;/h2&gt;&#xA;&lt;p&gt;Go 的气质，集中体现在几个和主流语言不一样的决定上。下面各用一小段代码点出它的样子、指向它在&#xA;本书的归属，并点明它换来了什么、付出了什么。&lt;/p&gt;</description>
    </item>
    <item>
      <title>1.3 顺序进程通讯</title>
      <link>/zh-cn/part1overview/ch01intro/csp/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch01intro/csp/</guid>
      <description>&lt;h1 id=&#34;13-顺序进程通讯&#34;&gt;1.3 顺序进程通讯&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;本节内容提供一个线上演讲：&lt;a href=&#34;https://www.youtube.com/watch?v=Z8ZpWVuEx8c&#34;&gt;YouTube 在线&lt;/a&gt;，&lt;a href=&#34;https://changkun.de/s/csp/&#34;&gt;Google Slides 讲稿&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Go 并发模型的思想源头，是 Hoare 于 1978 年在 CACM 上提出的&lt;strong&gt;顺序进程通讯&lt;/strong&gt;（Communicating&#xA;Sequential Processes, CSP）。这一节从思想史的角度交代这份血脉，它解释了 goroutine 与&#xA;channel 为何是今天这个样子。channel 与 select 的实现细节（hchan 的环形缓冲、&lt;code&gt;gopark&lt;/code&gt; 与&#xA;&lt;code&gt;goready&lt;/code&gt; 的配对、&lt;code&gt;selectgo&lt;/code&gt; 的两轮加锁），留待 &lt;a href=&#34;../../../part3concurrency/ch10chan/readme&#34;&gt;第 10 章&lt;/a&gt;&#xA;展开；这里关心的是「为什么是 CSP」，以及这份选择如何塑造了 Go 程序的样貌。&lt;/p&gt;&#xA;&lt;h2 id=&#34;131-csp-的核心主张&#34;&gt;1.3.1 CSP 的核心主张&lt;/h2&gt;&#xA;&lt;p&gt;上个世纪七十年代，多核处理器还是科研主题，尚未进入普通程序员的视野。彼时协调并发实体的手段&#xA;已经不少：信号量（Dijkstra, 1965）、监控（Hoare, 1974）、锁与互斥、消息传递。Lauer 与&#xA;Needham 1979 年还证明了「消息传递」与「共享内存加锁」在表达力上彼此等价，二者只是同一件事的&#xA;两副面孔。Hoare 在这片图景里做出的选择，是把通信（即输入输出）当作编程语言的基本要素，并以&#xA;此为唯一的协调手段。&lt;/p&gt;&#xA;&lt;p&gt;CSP 的核心是一句反直觉的主张：&lt;strong&gt;进程之间不共享状态，只通过传递消息来协调。&lt;/strong&gt; 在共享内存的&#xA;世界里，多个线程读写同一块内存，靠锁来防止彼此踩踏，这既危险（数据竞争、死锁），又难以推理。&#xA;CSP 反其道而行：每个顺序进程守着自己的状态，要协作就&lt;strong&gt;发消息&lt;/strong&gt;，通过一次同步的通信动作来交接&#xA;数据与控制权。Go 把这条主张凝成那句广为人知的格言：「&lt;strong&gt;不要以共享内存的方式通信，而要以通信的&#xA;方式共享内存。&lt;/strong&gt;」&lt;/p&gt;&#xA;&lt;p&gt;这里有一处需要澄清的史实，它常被一笔带过，却恰恰是理解 Go 与 CSP 关系的关键。&lt;/p&gt;&#xA;&lt;h2 id=&#34;132-1978-年的-csp按进程名通信没有-channel&#34;&gt;1.3.2 1978 年的 CSP：按进程名通信，没有 channel&lt;/h2&gt;&#xA;&lt;p&gt;读者若翻开 Hoare 1978 年的原始论文，会发现一件出乎意料的事：那门语言里&lt;strong&gt;没有第一类的&#xA;channel&lt;/strong&gt;。通信不是把数据投进某条匿名管道，而是&lt;strong&gt;直接指名某个进程&lt;/strong&gt;。两个基本算符是：&lt;/p&gt;</description>
    </item>
    <item>
      <title>2.1 Plan 9 汇编语言</title>
      <link>/zh-cn/part1overview/ch02asm/asm/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch02asm/asm/</guid>
      <description>&lt;h1 id=&#34;21-plan-9-汇编语言&#34;&gt;2.1 Plan 9 汇编语言&lt;/h1&gt;&#xA;&lt;p&gt;读 Go 运行时的源码，迟早会撞见一种看起来既像汇编、又不太像任何熟悉汇编的代码，那是 Go 的&#xA;&lt;strong&gt;Plan 9 风格汇编&lt;/strong&gt;。剖析调度器、栈切换、原子操作时我们会反复回到它：&lt;code&gt;gogo&lt;/code&gt;、&lt;code&gt;mcall&lt;/code&gt;、&#xA;&lt;code&gt;morestack&lt;/code&gt;、&lt;code&gt;asyncPreempt&lt;/code&gt; 都是汇编例程。本节解释它是什么、为何存在、以及读懂它需要的几个&#xA;关键概念。我们不求让读者会写 Plan 9 汇编，那是另一本书的内容，但求把它变成一份&lt;strong&gt;阅读词汇表&lt;/strong&gt;：&#xA;当后文提到某个汇编例程时，读者知道它身处的是怎样一个抽象层，每个符号各指向什么。&lt;/p&gt;&#xA;&lt;p&gt;本节示例取自 &lt;code&gt;runtime/asm_amd64.s&lt;/code&gt; 与 &lt;code&gt;internal/runtime/atomic&lt;/code&gt;。这些例程在各版本间相当稳定，&#xA;为聚焦设计，我们对个别与本节无关的实验性分支（如 &lt;code&gt;GOEXPERIMENT&lt;/code&gt; 守卫）做了裁剪，完整定义&#xA;请对照对应源文件。&lt;/p&gt;&#xA;&lt;h2 id=&#34;211-为什么-go-要有自己的汇编&#34;&gt;2.1.1 为什么 Go 要有自己的汇编&lt;/h2&gt;&#xA;&lt;p&gt;Go 的汇编源自 &lt;strong&gt;Plan 9 操作系统&lt;/strong&gt;的汇编器传统。Plan 9 是贝尔实验室在 Unix 之后的研究型系统，&#xA;Go 的几位设计者（Ken Thompson、Rob Pike、Russ Cox）正是从那里走来，把那套工具链的思路一并带进了&#xA;Go。它的关键特征是：&lt;strong&gt;它不是某一种 CPU 的原生汇编，而是一种半抽象、跨架构统一的中间汇编&lt;/strong&gt;。&#xA;同一套语法、同一批伪指令与伪寄存器，由工具链 &lt;code&gt;cmd/asm&lt;/code&gt; 翻译到 amd64、arm64、riscv64 等各目标&#xA;架构的真实指令。这与 GNU as 那种「每个架构一套方言」的设计截然不同。&lt;/p&gt;&#xA;&lt;p&gt;这个选择服务于 Go 最初的一个硬目标：&lt;strong&gt;一套工具链、交叉编译开箱即用&lt;/strong&gt;。当汇编层本身就是跨架构的，&#xA;为新架构移植运行时就退化成「为这套统一语法补一个后端」，而不是「重学一门汇编再重写一遍运行时」。&#xA;代价同样真实：Go 要自己维护汇编器、链接器与目标文件格式（&lt;code&gt;cmd/internal/obj&lt;/code&gt;），多养一整套基础设施。&#xA;买到的是对代码生成、调用约定、与运行时协同的&lt;strong&gt;完全掌控&lt;/strong&gt;，这条「自己造而非复用现成件」的取舍，&#xA;会在 &lt;a href=&#34;.././callconv&#34;&gt;2.3 调用约定&lt;/a&gt; 的自定义 ABI 与 &lt;a href=&#34;../../../part2lang/ch06func/func&#34;&gt;6.1&lt;/a&gt; 的函数调用里&#xA;再次出现，是理解 Go 运行时的一条主线。&lt;/p&gt;</description>
    </item>
    <item>
      <title>2.2 汇编中的栈帧与符号</title>
      <link>/zh-cn/part1overview/ch02asm/frame/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch02asm/frame/</guid>
      <description>&lt;h1 id=&#34;22-汇编中的栈帧与符号&#34;&gt;2.2 汇编中的栈帧与符号&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././asm&#34;&gt;2.1&lt;/a&gt; 给出了 Plan 9 汇编的伪寄存器与寻址语法，那是「怎么写一行汇编」。本节把视线&#xA;抬高到一段完整例程：一个手写汇编函数如何用 &lt;code&gt;TEXT ·name(SB)&lt;/code&gt; 声明自己的符号，如何用 &lt;code&gt;$framesize-argsize&lt;/code&gt;&#xA;申明栈帧大小，&lt;code&gt;NOSPLIT&lt;/code&gt; 又意味着什么。我们用运行时里三段真实例程（&lt;code&gt;Cas&lt;/code&gt;、&lt;code&gt;gogo&lt;/code&gt;、&lt;code&gt;morestack&lt;/code&gt;）&#xA;把这些约定逐处对上号，读完之后，调度器与栈管理章节里那些汇编符号就从天书变成可读的现场操作。&lt;/p&gt;&#xA;&lt;h2 id=&#34;221-从-go-源码到-plan-9-汇编cas-的逐槽对应&#34;&gt;2.2.1 从 Go 源码到 Plan 9 汇编：CAS 的逐槽对应&lt;/h2&gt;&#xA;&lt;p&gt;把抽象落到实处。运行时的原子比较交换 &lt;code&gt;Cas&lt;/code&gt; 在 amd64 上是手写汇编，它的 Go 原型与汇编实现&#xA;逐字段对得上，是观察「Go 源码如何映射到 Plan 9 汇编」的好样本。Go 这一侧只有声明（函数体在&#xA;&lt;code&gt;.s&lt;/code&gt; 文件里）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// internal/runtime/atomic：仅声明，实现在 atomic_amd64.s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;//&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//&#x9;if *ptr == old { *ptr = new; return true } else { return false }&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Cas&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;old&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;对应的 amd64 实现：&lt;/p&gt;</description>
    </item>
    <item>
      <title>2.3 调用约定与寄存器 ABI</title>
      <link>/zh-cn/part1overview/ch02asm/callconv/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch02asm/callconv/</guid>
      <description>&lt;h1 id=&#34;23-调用约定与寄存器-abi&#34;&gt;2.3 调用约定与寄存器 ABI&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;文中的寄存器名、栈帧布局与序言代码以 amd64 为例，&#xA;其余体系结构（arm64、riscv64 等）结构相同而寄存器名不同，可对照&#xA;&lt;code&gt;src/cmd/compile/abi-internal&lt;/code&gt; 的「Architecture specifics」一节。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;一次函数调用，在机器层面要回答一连串具体的问题：参数放哪里，返回值放哪里，谁负责保存哪些&#xA;寄存器，栈帧怎样布局，返回地址压在何处。把这些问题的答案固定下来，就是调用规范（calling&#xA;convention），它也常被称作 ABI（application binary interface）。ABI 不是某一段代码，而是一份&#xA;&lt;strong&gt;契约&lt;/strong&gt;：编译器生成的代码、手写的 Plan 9 汇编（&lt;a href=&#34;.././asm&#34;&gt;2.1&lt;/a&gt;）、运行时里的底层例程，三方各自&#xA;独立编写，却要在调用的那一刻严丝合缝地对接。契约一旦写定，三方就都按它来摆放数据，谁也不必&#xA;知道对方的内部细节。&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;../../../part2lang/ch06func/func&#34;&gt;6.1&lt;/a&gt; 已从语言视角讲过函数调用从栈到寄存器的演进，那里关心的是&#xA;「一次 &lt;code&gt;f(a, b)&lt;/code&gt; 在 Go 语义上发生了什么」。本节与 &lt;a href=&#34;.././args&#34;&gt;2.4 参数传递与栈帧布局&lt;/a&gt; 补足它在汇编与&#xA;运行时层面的另一半：同一次调用，落到机器指令上是如何约定的。两者合起来，函数调用这件事才算讲完整。&lt;/p&gt;&#xA;&lt;h2 id=&#34;231-两套-abiabi0-与-abiinternal&#34;&gt;2.3.1 两套 ABI：ABI0 与 ABIInternal&lt;/h2&gt;&#xA;&lt;p&gt;Go 同时维护着两套调用规范，理解它们的分工是读懂运行时里那些奇怪符号标注的钥匙。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;ABI0&lt;/strong&gt; 是早期的、&lt;strong&gt;基于栈&lt;/strong&gt;的规范：所有参数与返回值一律通过栈内存传递，调用方在自己的栈帧里&#xA;按顺序摆好参数，被调用方从固定偏移处取用。它的好处是布局稳定、可预测，人脑能算清每个参数的&#xA;偏移。手写汇编因此一律遵循 ABI0，&lt;code&gt;go.dev/doc/asm&lt;/code&gt; 描述的就是这套稳定 ABI。ABI0 还有一个常被&#xA;忽略的性质：它是 Go 唯一&lt;strong&gt;承诺稳定&lt;/strong&gt;的 ABI，是汇编与 Go 之间唯一可靠的接缝。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;ABIInternal&lt;/strong&gt; 是 Go 1.17 引入的、&lt;strong&gt;基于寄存器&lt;/strong&gt;的内部规范：尽量把参数与返回值放进寄存器，省去&#xA;大量进出栈内存的读写。所有由 Go 源码编译出的函数都走 ABIInternal。它的名字里「Internal」二字是&#xA;郑重的警告：这套 ABI &lt;strong&gt;不稳定&lt;/strong&gt;，会随 Go 版本变化，任何外部代码都不应依赖它的细节。换来的是&#xA;约 5% 的整体提速，且这份提速对用户代码完全透明，源码一字不改，重新编译即享。&lt;/p&gt;</description>
    </item>
    <item>
      <title>2.4 参数传递与栈帧布局</title>
      <link>/zh-cn/part1overview/ch02asm/args/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch02asm/args/</guid>
      <description>&lt;h1 id=&#34;24-参数传递与栈帧布局&#34;&gt;2.4 参数传递与栈帧布局&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././callconv&#34;&gt;2.3&lt;/a&gt; 给出了两套 ABI 的分工与参数分配的递归算法，那是「规则」。本节把规则落到&#xA;一个具体例子上：一个同时含标量与数组的签名，参数究竟在栈帧里怎样摆放，寄存器参数为何还要在&#xA;栈上留一格溢出槽，序言里那条几乎人人都付的栈增长检查又是怎么搭上抢占的便车。最后我们回到&#xA;「为何自定义 ABI」这个总账，把这两节合成一个完整的回答。&lt;/p&gt;&#xA;&lt;h2 id=&#34;241-一个掺杂栈传与寄存器传的例子&#34;&gt;2.4.1 一个掺杂栈传与寄存器传的例子&lt;/h2&gt;&#xA;&lt;p&gt;抽象规则不如一个例子来得清楚。取 ABI 规范里那个精心设计的签名，它故意同时含有能进寄存器的&#xA;标量与必须走栈的数组：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a3&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;y&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;按 &lt;a href=&#34;.././callconv&#34;&gt;2.3.1&lt;/a&gt; 的递归分配，在 amd64（整数寄存器 &lt;code&gt;RAX, RBX, ...&lt;/code&gt;）上结果是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;a1&lt;/code&gt;、&lt;code&gt;a3&lt;/code&gt; 是能进寄存器的标量，分到 &lt;code&gt;RAX&lt;/code&gt;、&lt;code&gt;RBX&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;a2&lt;/code&gt; 是非平凡数组，&lt;strong&gt;整个退回栈上&lt;/strong&gt;；&lt;/li&gt;&#xA;&lt;li&gt;返回值 &lt;code&gt;r1&lt;/code&gt; 含数组，也走栈；&lt;code&gt;r2&lt;/code&gt; 是 &lt;code&gt;string&lt;/code&gt;，拆成 base 与 len 两部分，回填 &lt;code&gt;RAX&lt;/code&gt;、&lt;code&gt;RBX&lt;/code&gt;&#xA;（返回时寄存器重新从头计数，与入参不冲突）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;于是入口时只有 &lt;code&gt;a2&lt;/code&gt; 在栈上有初值，栈帧布局如下，其余区域调用时一律不初始化：&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart TB&#xA;    spill[&amp;#34;a3Spill uint8&amp;lt;br/&amp;gt;a1Spill uint8&amp;lt;br/&amp;gt;（寄存器参数的溢出槽，调用方预留，调用时空着）&amp;#34;]&#xA;    ret[&amp;#34;r1.y [2]uintptr&amp;lt;br/&amp;gt;r1.x uintptr&amp;lt;br/&amp;gt;（栈传返回值）&amp;#34;]&#xA;    arg[&amp;#34;a2 [2]uintptr&amp;lt;br/&amp;gt;（栈传参数，唯一带初值者）&amp;#34;]&#xA;    spill --&amp;gt; ret --&amp;gt; arg&#xA;    low[&amp;#34;↓ 低地址&amp;#34;]&#xA;    arg -.-&amp;gt; low&lt;/pre&gt;&#xA;&lt;p&gt;这个例子把三件事一次说清：标量优先进寄存器，含数组的值整体走栈，而&lt;strong&gt;每个寄存器参数仍在栈上&#xA;留有一格溢出槽&lt;/strong&gt;待命。把它和 &lt;a href=&#34;.././callconv&#34;&gt;2.3.1&lt;/a&gt; 那条「全栈传」的 ABI0 对照，差别一目了然：ABI0&#xA;下 &lt;code&gt;a1&lt;/code&gt;、&lt;code&gt;a3&lt;/code&gt; 连同 &lt;code&gt;a2&lt;/code&gt; 全在栈上按序排开，没有寄存器、也无需溢出槽；ABIInternal 把能搬的都搬进了&#xA;寄存器，省下的正是那几次进出栈内存的读写。&lt;/p&gt;</description>
    </item>
    <item>
      <title>3.1 从 `go` 命令谈起</title>
      <link>/zh-cn/part1overview/ch03life/cmd/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch03life/cmd/</guid>
      <description>&lt;h1 id=&#34;31-从-go-命令谈起&#34;&gt;3.1 从 &lt;code&gt;go&lt;/code&gt; 命令谈起&lt;/h1&gt;&#xA;&lt;p&gt;Go 程序的生命周期要从执行 &lt;code&gt;go&lt;/code&gt; 命令开始谈起。读者每天敲下的 &lt;code&gt;go build&lt;/code&gt;、&lt;code&gt;go test&lt;/code&gt;、&lt;code&gt;go run&lt;/code&gt;，&#xA;看上去只是「把源码变成二进制」，背后却藏着一个常被误解的事实：&lt;code&gt;go&lt;/code&gt; 本身并不是编译器。它是一个&#xA;&lt;strong&gt;构建编排器&lt;/strong&gt;（build orchestrator），负责把一次构建分解成许多道工序，安排它们的先后与并行，&#xA;再逐道调用真正的工具，编译器 &lt;code&gt;compile&lt;/code&gt;（&lt;a href=&#34;.././compile&#34;&gt;3.2&lt;/a&gt;）、汇编器 &lt;code&gt;asm&lt;/code&gt;、链接器 &lt;code&gt;link&lt;/code&gt;&#xA;（&lt;a href=&#34;.././link&#34;&gt;3.4&lt;/a&gt;）。本节先把这层编排关系讲清楚，它是后续几节的总纲：读懂了 &lt;code&gt;go&lt;/code&gt; 如何把一次&#xA;构建拆成一张图、如何用内容寻址的缓存避免重复劳动，再去看编译与链接的细节，就有了落脚的框架。&lt;/p&gt;&#xA;&lt;h2 id=&#34;311-go-是构建编排器不是编译器&#34;&gt;3.1.1 &lt;code&gt;go&lt;/code&gt; 是构建编排器，不是编译器&lt;/h2&gt;&#xA;&lt;p&gt;把一个最小程序的构建过程摊开，最直接的办法是 &lt;code&gt;go build -x&lt;/code&gt;，它会打印出 &lt;code&gt;go&lt;/code&gt; 实际执行的每一条&#xA;子命令。对一个只有一行 &lt;code&gt;fmt.Println&lt;/code&gt; 的 &lt;code&gt;main&lt;/code&gt; 包，输出的骨架是这样：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ go build -x -o /dev/null .&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;WORK&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/tmp/go-build1449454186&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir -p &lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;/b001/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat &amp;gt;&lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;/b001/importcfg &lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39; # internal&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;# import config&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;packagefile fmt=/Users/.../go-build/7d/7d74...-d&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;packagefile runtime=/Users/.../go-build/60/60cb...-d&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;EOF&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 调用编译器，把 main.go 编成归档 _pkg_.a&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.../pkg/tool/darwin_arm64/compile -o &lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;/b001/_pkg_.a -p main -lang&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;go1.26 &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    -complete -buildid ... -importcfg &lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;/b001/importcfg -pack ./main.go&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.../pkg/tool/darwin_arm64/buildid -w &lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;/b001/_pkg_.a &lt;span class=&#34;c1&#34;&gt;# internal&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cp &lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;/b001/_pkg_.a /Users/.../go-build/82/8256...-d &lt;span class=&#34;c1&#34;&gt;# internal&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 写出链接配置，再调用链接器&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat &amp;gt;&lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;/b001/importcfg.link &lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39; # internal&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;packagefile demo=$WORK/b001/_pkg_.a&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;packagefile fmt=/Users/.../go-build/7d/7d74...-d&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;EOF&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.../pkg/tool/darwin_arm64/link -o &lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;/b001/exe/a.out -importcfg ... &lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;/b001/_pkg_.a&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;go&lt;/code&gt; 自己做的事，是建一个临时工作目录、写出一份份 &lt;code&gt;importcfg&lt;/code&gt;（告诉编译器与链接器每个依赖包的&#xA;归档在磁盘何处），然后 &lt;code&gt;exec&lt;/code&gt; 出 &lt;code&gt;compile&lt;/code&gt; 与 &lt;code&gt;link&lt;/code&gt; 这两个独立的可执行文件。源文件到目标代码&#xA;的真正翻译，发生在 &lt;code&gt;go&lt;/code&gt; 之外的工具里。这种「主程序只负责编排、把脏活交给独立工具」的结构，在&#xA;&lt;code&gt;go&lt;/code&gt; 命令的内部用两个数据类型承载：&lt;code&gt;Builder&lt;/code&gt; 持有整次构建的共享状态（工作目录、各类缓存、并行度&#xA;控制），&lt;code&gt;Action&lt;/code&gt; 则是构建图上的一个节点，描述「一道要做的工序」：&lt;/p&gt;</description>
    </item>
    <item>
      <title>3.2 Go 程序编译流程</title>
      <link>/zh-cn/part1overview/ch03life/compile/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch03life/compile/</guid>
      <description>&lt;h1 id=&#34;32-go-程序编译流程&#34;&gt;3.2 Go 程序编译流程&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././cmd&#34;&gt;3.1&lt;/a&gt; 说 &lt;code&gt;go build&lt;/code&gt; 背后真正干活的是 &lt;code&gt;compile&lt;/code&gt; 与 &lt;code&gt;link&lt;/code&gt; 两支程序。这一节把镜头推近到&#xA;&lt;code&gt;compile&lt;/code&gt;，给出它从一份 &lt;code&gt;.go&lt;/code&gt; 源文件到一个 &lt;code&gt;.o&lt;/code&gt; 目标文件之间走过的全程：每个阶段拿到什么、&#xA;吐出什么、为什么这样切分。本节是编译流水线的&lt;strong&gt;全景图&lt;/strong&gt;，每个阶段的内部机理（文法如何设计、&#xA;类型检查用什么算法、SSA 的优化规则）留给 &lt;a href=&#34;../../../part5toolchain/ch15compile/readme&#34;&gt;第 15 章&lt;/a&gt;&#xA;逐一展开，这里只负责把它们串成一条线，并指出两条贯穿始终的暗线。&lt;/p&gt;&#xA;&lt;p&gt;为避免落回逐字翻译编译器目录结构的窠臼，下文用&lt;strong&gt;一个贯穿全程的小例子&lt;/strong&gt;来串起所有阶段。&#xA;请记住这段函数，它会被流水线反复揉捏，直到变成机器码：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;send&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;chan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;go&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;work&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 启动一个新 goroutine&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 向 channel 发送&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;它只有两行，却恰好藏着本节要强调的第二条暗线：&lt;code&gt;go&lt;/code&gt; 与 &lt;code&gt;&amp;lt;-&lt;/code&gt; 这样的语言关键字，最终都不是&#xA;靠 CPU 指令直接实现的，而是被编译器&lt;strong&gt;翻译成对运行时的函数调用&lt;/strong&gt;。先把流水线看完，再回头&#xA;解剖这两行。&lt;/p&gt;&#xA;&lt;h2 id=&#34;321-两条暗线为何要先讲它们&#34;&gt;3.2.1 两条暗线：为何要先讲它们&lt;/h2&gt;&#xA;&lt;p&gt;把流水线的细节铺开之前，先点明两个贯穿全程的设计取向，后面每个阶段都在为它们服务。&lt;/p&gt;&#xA;&lt;p&gt;第一条暗线是&lt;strong&gt;编译速度本身是一等约束&lt;/strong&gt;。Go 从设计之初就把「编译要快」当作语言目标而非事后&#xA;优化（&lt;a href=&#34;../../ch01intro/history&#34;&gt;1.1&lt;/a&gt;）。这个目标渗进了语法、依赖管理与编译器结构的每一层：&#xA;文法被设计成可以不回溯地快速解析，依赖以编译期产出的&lt;strong&gt;紧凑导出数据&lt;/strong&gt;而非源码传递，且不存在&#xA;C/C++ 那种会层层传染的 &lt;code&gt;#include&lt;/code&gt;。Rob Pike 在《Go at Google》里把这一点列为 Go 诞生的直接&#xA;动机，当年 Google 一次大型 C++ 构建动辄以小时计，而 Go 要做到「按一下回车就编译完」。&lt;/p&gt;</description>
    </item>
    <item>
      <title>3.3 语言的自举</title>
      <link>/zh-cn/part1overview/ch03life/bootstrap/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch03life/bootstrap/</guid>
      <description>&lt;h1 id=&#34;33-语言的自举&#34;&gt;3.3 语言的自举&lt;/h1&gt;&#xA;&lt;p&gt;一个问题听起来像悖论：Go 的编译器、汇编器、链接器与运行时如今都用 Go 写成，那么第一个能编译&#xA;Go 的程序是从哪里来的？没有 Go 编译器，怎么编译出 Go 编译器？这就是&lt;strong&gt;自举&lt;/strong&gt;（bootstrapping）。&#xA;它既是「先有鸡还是先有蛋」的经典困局，也是 Go 工具链演化史上一段可被精确复述的工程。本节&#xA;回答三件事：这只蛋最初是怎么孵出来的；自举之后，构建一个新版 Go 的链条如何运转，以及它对&#xA;引导版本的要求为何逐年抬升；最后，为什么一门语言「用自己实现自己」值得郑重对待。&lt;/p&gt;&#xA;&lt;h2 id=&#34;331-先有蛋从-c-写的工具链到-go-写的工具链&#34;&gt;3.3.1 先有蛋：从 C 写的工具链到 Go 写的工具链&lt;/h2&gt;&#xA;&lt;p&gt;自举的前提，是先有一个不依赖自身的起点。Go 工具链最初（到 &lt;strong&gt;Go 1.4&lt;/strong&gt;，2014 年）的编译器、&#xA;汇编器、链接器与 &lt;code&gt;cmd/dist&lt;/code&gt; 都是用 &lt;strong&gt;C&lt;/strong&gt; 写的，编译器沿用了 Plan 9 的命名（&lt;code&gt;5c&lt;/code&gt;、&lt;code&gt;6c&lt;/code&gt;、&lt;code&gt;8c&lt;/code&gt;、&#xA;&lt;code&gt;9c&lt;/code&gt; 等，数字对应不同的目标架构）。这套 C 程序由系统自带的 &lt;code&gt;gcc&lt;/code&gt; 或 &lt;code&gt;clang&lt;/code&gt; 编译，于是构建&#xA;Go 不需要任何已存在的 Go,这便是那只「蛋」。&lt;/p&gt;&#xA;&lt;p&gt;从 &lt;strong&gt;Go 1.5（2015 年 8 月）&lt;strong&gt;起，Go 完成了一次里程碑式的转变：编译器与运行时被&lt;/strong&gt;整体用 Go&#xA;重写&lt;/strong&gt;，C 工具链被删除，工具链从此可以自我引导。这步棋的代价与收益都很实在。收益是，编译器&#xA;开发者从此用 Go 这门内存安全、并发友好、自带测试与剖析工具的语言来维护工具链，编译器里的&#xA;bug 可以用 Go 自己的调试手段去查；运行时与编译器共用一套语言，跨边界的协作（如逃逸分析与&#xA;栈管理）也更顺。代价是，构建 Go 不再像编译一段 C 那样自给自足，它现在需要「另一个已经能用的&#xA;Go」。如何用一个旧的 Go 造出一个新的 Go，就成了此后每个版本都要回答的问题。&lt;/p&gt;</description>
    </item>
    <item>
      <title>3.4 模块链接</title>
      <link>/zh-cn/part1overview/ch03life/link/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch03life/link/</guid>
      <description>&lt;h1 id=&#34;34-模块链接&#34;&gt;3.4 模块链接&lt;/h1&gt;&#xA;&lt;p&gt;编译器（&lt;a href=&#34;.././compile&#34;&gt;3.2&lt;/a&gt;）把每个包变成一个目标文件，但目标文件还不能直接运行。它们彼此&#xA;引用着对方的函数与变量，地址尚未确定，还缺运行时的支撑。把这些碎片拼成一个完整、可加载执行&#xA;的程序，是&lt;strong&gt;链接器&lt;/strong&gt;（&lt;code&gt;cmd/link&lt;/code&gt;，通常以 &lt;code&gt;go tool link&lt;/code&gt; 的形式被 &lt;code&gt;go build&lt;/code&gt; 调起）的活。这一节&#xA;看链接器做了哪几件事，以及 Go 在链接上的几处独特选择,它们一同解释了为什么一个 Go 程序往往是&#xA;一个「拷过去就能跑」的自包含文件。&lt;/p&gt;&#xA;&lt;h2 id=&#34;341-链接器做的事&#34;&gt;3.4.1 链接器做的事&lt;/h2&gt;&#xA;&lt;p&gt;链接器的输入是一个 main 包的目标文件，加上它递归依赖的所有包（含整个运行时）的目标文件；&#xA;输出是一个可被操作系统加载执行的二进制。中间它要顺序完成几件关键工作：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;符号解析&lt;/strong&gt;（symbol resolution）：把每一处对外部符号的&lt;strong&gt;引用&lt;/strong&gt;，绑定到它在某个目标文件里的&#xA;唯一&lt;strong&gt;定义&lt;/strong&gt;。一个符号就是一个有名字的地址,某个函数的入口、某个全局变量的存储。A 包里写&#xA;&lt;code&gt;fmt.Println(...)&lt;/code&gt;，编译 A 时并不知道 &lt;code&gt;fmt.Println&lt;/code&gt; 在哪，只留下一个待解析的引用；链接器把它&#xA;接到 &lt;code&gt;fmt&lt;/code&gt; 包目标文件里那份定义上。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;布局&lt;/strong&gt;（layout）：把所有符号的机器码与数据，按种类安排进可执行文件的各个段,代码进 text、&#xA;可读写数据进 data、只读数据（字符串字面量、类型信息）进 rodata 等。布局一旦定下，每个符号的&#xA;最终地址也就确定了。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;重定位&lt;/strong&gt;（relocation）：地址定下后，回头修正所有引用处填的临时地址。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;死代码消除&lt;/strong&gt;（dead-code elimination）：从入口出发不可达的函数与变量，根本不写进最终二进制。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;最后产出的，是一个静态布局完毕、地址全部填实的文件。下面三节分别细看其中最值得说的三处：&#xA;重定位（3.4.2）、死代码消除（3.4.3）、以及「运行时也被一并链接进来」这一 Go 的标志性后果&#xA;（3.4.4）。&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    OBJ[&amp;#34;main.a &amp;#43; 依赖包目标文件&amp;lt;br/&amp;gt;(含整个 runtime)&amp;#34;] --&amp;gt; LOAD[&amp;#34;装载符号&amp;lt;br/&amp;gt;loader 读入&amp;#34;]&#xA;    LOAD --&amp;gt; RESOLVE[&amp;#34;符号解析&amp;lt;br/&amp;gt;引用 → 定义&amp;#34;]&#xA;    RESOLVE --&amp;gt; DEAD[&amp;#34;死代码消除&amp;lt;br/&amp;gt;从 main.main 标记可达&amp;#34;]&#xA;    DEAD --&amp;gt; LAYOUT[&amp;#34;段布局&amp;lt;br/&amp;gt;text/data/rodata&amp;#34;]&#xA;    LAYOUT --&amp;gt; RELOC[&amp;#34;重定位&amp;lt;br/&amp;gt;填实地址&amp;#34;]&#xA;    RELOC --&amp;gt; BIN[&amp;#34;可执行二进制&amp;#34;]&lt;/pre&gt;&#xA;&lt;h2 id=&#34;342-符号解析与重定位&#34;&gt;3.4.2 符号解析与重定位&lt;/h2&gt;&#xA;&lt;p&gt;这两步是链接的核心机制，值得稍微形式化一点。&lt;/p&gt;</description>
    </item>
    <item>
      <title>3.5 Go 程序启动引导</title>
      <link>/zh-cn/part1overview/ch03life/boot/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch03life/boot/</guid>
      <description>&lt;h1 id=&#34;35-go-程序启动引导&#34;&gt;3.5 Go 程序启动引导&lt;/h1&gt;&#xA;&lt;p&gt;读者写下的 &lt;code&gt;main&lt;/code&gt; 函数，并不是程序真正的第一条指令。当操作系统把控制权交给一个 Go&#xA;可执行文件时，跑起来的是运行时（runtime）：它要在主线程上铺好执行栈、绑定线程本地存储、&#xA;量出 CPU 核心数与内存物理页大小，把内存分配器、垃圾回收器、调度器逐一唤醒，最后才创建那个&#xA;承载 &lt;code&gt;main&lt;/code&gt; 的 goroutine，交由调度循环去运行。换言之，一个 Go 二进制文件里随身带着一个&#xA;微型操作系统（&lt;a href=&#34;../../ch01intro/go&#34;&gt;1.2&lt;/a&gt;），它先于用户代码自行启动。本节顺着这条引导链走一遍，&#xA;从操作系统的入口符号，到第一个 goroutine 被调度，看清楚「在 &lt;code&gt;main&lt;/code&gt; 之前」究竟发生了什么。&lt;/p&gt;&#xA;&lt;p&gt;引导链的脉络可以先记住三段：汇编入口在主线程上手工搭出 &lt;code&gt;g0&lt;/code&gt; 与 &lt;code&gt;m0&lt;/code&gt;（&lt;a href=&#34;/zh-cn/part1overview/ch03life/boot/#351-%e6%b1%87%e7%bc%96%e5%85%a5%e5%8f%a3g0m0-%e4%b8%8e-tls&#34;&gt;3.5.1&lt;/a&gt;），&#xA;&lt;code&gt;schedinit&lt;/code&gt; 按依赖次序点亮各个子系统（&lt;a href=&#34;/zh-cn/part1overview/ch03life/boot/#352-schedinit%e6%8c%89%e6%ac%a1%e5%ba%8f%e5%94%a4%e9%86%92%e8%bf%90%e8%a1%8c%e6%97%b6&#34;&gt;3.5.2&lt;/a&gt;），随后&#xA;&lt;code&gt;newproc&lt;/code&gt; 造出第一个 goroutine、&lt;code&gt;mstart&lt;/code&gt; 进入调度循环把它跑起来（&lt;a href=&#34;/zh-cn/part1overview/ch03life/boot/#353-%e7%ac%ac%e4%b8%80%e4%b8%aa-goroutine-%e4%b8%8e%e8%b0%83%e5%ba%a6%e5%be%aa%e7%8e%af&#34;&gt;3.5.3&lt;/a&gt;）。&lt;/p&gt;&#xA;&lt;h2 id=&#34;351-汇编入口g0m0-与-tls&#34;&gt;3.5.1 汇编入口：g0、m0 与 TLS&lt;/h2&gt;&#xA;&lt;p&gt;运行时的真正入口由 runtime 包以汇编写就。以 AMD64 为例，Linux 与 macOS 的入口符号分别位于&#xA;&lt;code&gt;runtime/rt0_linux_amd64.s&lt;/code&gt; 与 &lt;code&gt;runtime/rt0_darwin_amd64.s&lt;/code&gt;，两者都只是一跳，转入共用的&#xA;&lt;code&gt;_rt0_amd64&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-asm&#34; data-lang=&#34;asm&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;TEXT&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;_rt0_amd64_linux&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;SB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;NOSPLIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;$-8&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;nf&#34;&gt;JMP&lt;/span&gt;&#x9;&lt;span class=&#34;no&#34;&gt;_rt0_amd64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;SB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;TEXT&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;_rt0_amd64_darwin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;SB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;NOSPLIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;$-8&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#x9;&lt;span class=&#34;nf&#34;&gt;JMP&lt;/span&gt;&#x9;&lt;span class=&#34;no&#34;&gt;_rt0_amd64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;SB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;为何按「架构 + 操作系统」两维拆分入口？因为程序编译为机器码后，指令集只取决于 CPU 架构，&#xA;而操作系统的差异体现在系统调用等系统级操作上。一份 &lt;code&gt;_rt0_amd64&lt;/code&gt; 可被多个操作系统共用，&#xA;各操作系统再以自己的入口符号跳进来。&lt;code&gt;rt0&lt;/code&gt; 是 &lt;code&gt;runtime0&lt;/code&gt; 的缩写，意指运行时的创生，&#xA;此后所有派生出来的同类对象都以 &lt;code&gt;1&lt;/code&gt; 为后缀，&lt;code&gt;g0&lt;/code&gt;、&lt;code&gt;m0&lt;/code&gt; 即得名于此。&lt;/p&gt;</description>
    </item>
    <item>
      <title>3.6 主 Goroutine 的生与死</title>
      <link>/zh-cn/part1overview/ch03life/main/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part1overview/ch03life/main/</guid>
      <description>&lt;h1 id=&#34;36-主-goroutine-的生与死&#34;&gt;3.6 主 Goroutine 的生与死&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././boot&#34;&gt;3.5&lt;/a&gt; 里 &lt;code&gt;schedinit&lt;/code&gt; 把运行时的家底备齐之后并不直接调用 &lt;code&gt;runtime.main&lt;/code&gt;，而是把它&#xA;的入口地址压栈、交给 &lt;code&gt;newproc&lt;/code&gt; 造出第一个 Goroutine，再由 &lt;code&gt;mstart&lt;/code&gt; 启动调度循环、把这个&#xA;Goroutine 挑出来执行。调度的细节留到 &lt;a href=&#34;/zh-cn/part3concurrency/ch09sched/&#34;&gt;9 调度器&lt;/a&gt; 详谈，本节&#xA;只把镜头对准一个时刻：&lt;strong&gt;第一个 Goroutine 已经在跑，它正要执行 &lt;code&gt;runtime.main&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;这只 Goroutine 与众不同。它是程序里诞生的第一个，承载着用户的 &lt;code&gt;main.main&lt;/code&gt;，也独自掌握着整个&#xA;进程的生杀大权：它一返回，进程就结束，哪怕别处还有上千个 Goroutine 正忙。理解它做了什么、&#xA;以及它何时收手，是理解「一个 Go 程序如何开始、又如何整体退出」的最后一块拼图。&lt;/p&gt;&#xA;&lt;h2 id=&#34;361-runtimemain用户代码之前的铺垫&#34;&gt;3.6.1 &lt;code&gt;runtime.main&lt;/code&gt;：用户代码之前的铺垫&lt;/h2&gt;&#xA;&lt;p&gt;运行时包里的 &lt;code&gt;main&lt;/code&gt; 函数（即 &lt;code&gt;runtime.main&lt;/code&gt;）与用户的 &lt;code&gt;main.main&lt;/code&gt; 跑在同一个 Goroutine 上，&#xA;但在把控制权交给用户之前，它先料理了几桩只能由「第一个 Goroutine」来做的事。下面是裁剪后的&#xA;速写，只留与生命周期相关的骨架：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;44&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;45&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;46&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 主 Goroutine 入口（裁剪自 runtime/proc.go）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;getg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 执行栈上限：64 位 1GB，32 位 250MB（用十进制，崩溃信息里更好看）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;goarch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PtrSize&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;maxstacksize&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1000000000&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;maxstacksize&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;250000000&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mainStarted&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 允许 newproc 启动新的 M&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;haveSysmon&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;               &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 启动系统监控线程（见 9.8）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;systemstack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;newm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sysmon&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;lockOSThread&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 初始化期间把主 G 锁在主 OS 线程上&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;throw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;runtime.main not on m0&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runtimeInitTime&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;nanotime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 记下「世界开始」的时刻，须在 doInit 之前&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;doInit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runtime_inittasks&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 运行运行时自身的 init（含 GC、defer 类型等）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;gcenable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 启用垃圾回收器（见 13）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 按依赖顺序运行所有模块（含用户包）的 init 任务&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lastmoduledatap&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;firstmoduledata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;doInit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;inittasks&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;unlockOSThread&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;main_main&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;               &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 间接调用：链接器此时不知道 main 包的地址&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;fn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;exit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 主 G 一返回，整个进程退出&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这段骨架里有几处值得停下来看。&lt;/p&gt;</description>
    </item>
    <item>
      <title>4.1 运行时类型系统</title>
      <link>/zh-cn/part2lang/ch04type/type/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch04type/type/</guid>
      <description>&lt;h1 id=&#34;41-运行时类型系统&#34;&gt;4.1 运行时类型系统&lt;/h1&gt;&#xA;&lt;p&gt;Go 是静态类型语言，类型检查发生在编译期。但 Go 又保留了相当一部分&lt;strong&gt;运行时类型信息&lt;/strong&gt;（runtime&#xA;type information, RTTI），正是它支撑起了接口（&lt;a href=&#34;.././interface&#34;&gt;4.2&lt;/a&gt;）、类型断言、类型 switch、&#xA;反射，以及垃圾回收对指针的精确识别。这一节回答一个看似简单的问题：编译期那套类型，到了运行时&#xA;还剩下什么、以什么形式留存、又撑起了哪些能力。读懂这一节里那个叫&lt;strong&gt;类型描述符&lt;/strong&gt;的小结构，&#xA;后面的接口（&lt;a href=&#34;.././interface&#34;&gt;4.2&lt;/a&gt;）、反射与精确 GC（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/&#34;&gt;13&lt;/a&gt;）就都有了共同的&#xA;落脚点。&lt;/p&gt;&#xA;&lt;p&gt;下文给出的结构体都是&lt;strong&gt;裁剪后的速写&lt;/strong&gt;：只保留与设计相关的字段，并在注释里说明它为何存在。&#xA;完整定义可对照 go1.26 的 &lt;code&gt;internal/abi/type.go&lt;/code&gt; 与 &lt;code&gt;reflect&lt;/code&gt; 包。&lt;/p&gt;&#xA;&lt;h2 id=&#34;411-每个类型都有一个描述符&#34;&gt;4.1.1 每个类型都有一个描述符&lt;/h2&gt;&#xA;&lt;p&gt;编译器为程序里用到的&lt;strong&gt;每一个类型&lt;/strong&gt;生成一份&lt;strong&gt;类型描述符&lt;/strong&gt;，在 go1.26 的源码里它叫 &lt;code&gt;abi.Type&lt;/code&gt;，&#xA;运行时内部沿用旧名 &lt;code&gt;_type&lt;/code&gt;。描述符被编进只读数据段，整个程序运行期间常驻不变。同一个类型&#xA;在一个程序里通常只有一份描述符，这一点后面判断「类型是否相同」时会用到。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// abi.Type：一个类型在运行时的表示（裁剪后的速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Size_&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 该类型一个值占多少字节，分配与拷贝都要它&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PtrBytes&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 值的前若干字节里可能含指针，GC 只需扫到这里&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Hash&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 类型的哈希，类型 switch、接口表查找时避免重算&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TFlag&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TFlag&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 标志位：是否具名、是否带 UncommonType、GC 位图是否按需生成&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Align_&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 该类型变量的对齐&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FieldAlign_&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 作为结构体字段时的对齐&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kind_&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 种类：int、struct、slice、chan… 决定如何解释这块内存&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 比较该类型两个值是否相等：(指向 A, 指向 B) -&amp;gt; ==?&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 编译器按类型结构合成，map、接口比较、== 运算都落到它身上&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Equal&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// GC 指针位图：标出该类型哪些字（word）是指针，精确 GC 据此扫描&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GCData&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Str&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;NameOff&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 类型名在名字表里的偏移（按需解出可读名字）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PtrToThis&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TypeOff&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 指向「*T」描述符的偏移，构造指针类型时复用&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;每个字段都为某种运行时能力而存在，挑几样要紧的说。&lt;code&gt;Size_&lt;/code&gt; 是分配器（&lt;a href=&#34;/zh-cn/part4memory/ch12alloc/&#34;&gt;12&lt;/a&gt;）&#xA;和值拷贝的依据。&lt;code&gt;Kind_&lt;/code&gt; 是一个枚举，告诉运行时「这块内存按哪种类型解释」，它的取值是有限的&#xA;二十余种基础种类：&lt;/p&gt;</description>
    </item>
    <item>
      <title>4.2 接口</title>
      <link>/zh-cn/part2lang/ch04type/interface/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch04type/interface/</guid>
      <description>&lt;h1 id=&#34;42-接口&#34;&gt;4.2 接口&lt;/h1&gt;&#xA;&lt;p&gt;接口是 Go 类型系统的灵魂。它让「行为」与「实现」解耦，又用一种与众不同的方式做这件事：&#xA;结构化、隐式的满足，区别于绝大多数主流语言。这一节看接口在运行时怎么表示、方法怎么动态&#xA;分发、类型断言怎么落地，以及这套设计放进多态的谱系里处在哪个位置。沿用前几节的做法，下文&#xA;给出的结构体都是&lt;strong&gt;裁剪后的速写&lt;/strong&gt;：只留与设计相关的字段，注释说明它为何存在。完整定义可对照&#xA;&lt;code&gt;runtime/runtime2.go&lt;/code&gt;、&lt;code&gt;internal/abi/iface.go&lt;/code&gt; 与 &lt;code&gt;runtime/iface.go&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;421-两种表示iface-与-eface&#34;&gt;4.2.1 两种表示：iface 与 eface&lt;/h2&gt;&#xA;&lt;p&gt;接口值在运行时一律是&lt;strong&gt;两个字宽&lt;/strong&gt;，但分两种。带方法的接口（&lt;strong&gt;非空接口&lt;/strong&gt;）用 &lt;code&gt;iface&lt;/code&gt;：一个指向&#xA;&lt;code&gt;itab&lt;/code&gt; 的指针，加一个指向具体数据的指针。空接口 &lt;code&gt;interface{}&lt;/code&gt;（即 &lt;code&gt;any&lt;/code&gt;）用 &lt;code&gt;eface&lt;/code&gt;：一个指向&#xA;类型信息 &lt;code&gt;_type&lt;/code&gt; 的指针，加一个数据指针。空接口没有方法，不需要方法表，于是直接存类型即可，&#xA;省下一层间接：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 非空接口：带方法，故需方法表 itab（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;iface&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tab&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;itab&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 接口类型 × 具体类型 的方法表（含动态类型与 Fun 表）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 指向具体值（大于一字或需取址时为堆/栈上副本的指针）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 空接口 any：无方法，直接挂类型信息（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;eface&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 具体值的动态类型&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;data&lt;/code&gt; 存的是指针而非值本身。把一个 &lt;code&gt;int&lt;/code&gt; 放进接口，运行时会把它装箱（boxing）到一处可寻址&#xA;的内存，再让 &lt;code&gt;data&lt;/code&gt; 指过去，这也是「接口会引入一次分配」这一性能直觉的来源。两字表示是理解&#xA;后续一切的地基：动态分发、类型断言、乃至下面的 nil 陷阱，都是它的直接推论。&lt;/p&gt;</description>
    </item>
    <item>
      <title>4.3 类型别名</title>
      <link>/zh-cn/part2lang/ch04type/alias/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch04type/alias/</guid>
      <description>&lt;h1 id=&#34;43-类型别名&#34;&gt;4.3 类型别名&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;type A = B&lt;/code&gt;（类型别名）与 &lt;code&gt;type A B&lt;/code&gt;（定义新类型）只差一个等号，语义却根本不同。前者只是&#xA;给一个已存在的类型取个新名字，后者会造出一个全新的、有独立标识的类型。这个区别看似细微，却&#xA;牵动着类型相等、方法集、可赋值性（&lt;a href=&#34;.././type&#34;&gt;4.1&lt;/a&gt;）这一整套规则，也牵动着一个 Go 一贯关心的&#xA;工程问题：当一份代码不再由原作者维护、却仍要在不破坏兼容的前提下演化时，类型该如何在包之间&#xA;迁移。这一节先把别名与定义类型的语义讲清，再交代别名为何在 Go 1.9 被引入，最后看它在 Go 1.24&#xA;迎来的泛型能力。&lt;/p&gt;&#xA;&lt;h2 id=&#34;431-别名-vs-定义类型&#34;&gt;4.3.1 别名 vs 定义类型&lt;/h2&gt;&#xA;&lt;p&gt;按语言规范，类型声明分两种形式：别名声明（alias declaration）与类型定义（type definition）。&#xA;二者的分界就是那个等号。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;type Celsius float64&lt;/code&gt; 是类型定义，它「创建出一个新的、不同的类型，与给定类型有相同的底层&#xA;类型与操作」。&lt;code&gt;Celsius&lt;/code&gt; 与 &lt;code&gt;float64&lt;/code&gt; 从此是两个不同的名义类型（&lt;a href=&#34;.././type&#34;&gt;4.1&lt;/a&gt;）：各有自己的&#xA;标识，可以挂自己的方法，彼此之间不能直接赋值，只能显式转换。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;type byte = uint8&lt;/code&gt; 是别名声明，它只是「把一个标识符绑定到给定类型」。在该标识符的作用域内，&#xA;&lt;code&gt;byte&lt;/code&gt; 就「充当 uint8 的别名」，二者是&lt;strong&gt;同一个类型&lt;/strong&gt;的两个名字，完全可以互换。标准库里&#xA;&lt;code&gt;byte&lt;/code&gt;（&lt;code&gt;= uint8&lt;/code&gt;）、&lt;code&gt;rune&lt;/code&gt;（&lt;code&gt;= int32&lt;/code&gt;），以及 Go 1.18 起 &lt;code&gt;any&lt;/code&gt;（&lt;code&gt;= interface{}&lt;/code&gt;），正是这样&#xA;定义的预声明别名。&lt;/p&gt;&#xA;&lt;p&gt;别名与定义类型最容易混淆、也最能体现差别的地方，是&lt;strong&gt;方法集&lt;/strong&gt;。看一段会编译失败的代码：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;bytes&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;AliasBuf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Buffer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 别名：就是 bytes.Buffer 本身&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;DefBuf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Buffer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 定义类型：底层相同，但方法集为空&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;demo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;AliasBuf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;WriteString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;x&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// OK：AliasBuf 与 bytes.Buffer 是同一类型，方法集完全相同&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;DefBuf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;WriteString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;x&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 编译错误：DefBuf 未继承 bytes.Buffer 的任何方法&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Buffer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 但可显式转换回去：二者底层类型相同&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;DefBuf&lt;/code&gt; 与 &lt;code&gt;bytes.Buffer&lt;/code&gt; 底层类型相同，因此可以互相显式转换，但 &lt;code&gt;DefBuf&lt;/code&gt; 是一个&lt;strong&gt;新类型&lt;/strong&gt;，&#xA;它不继承 &lt;code&gt;bytes.Buffer&lt;/code&gt; 的方法集，&lt;code&gt;d.WriteString&lt;/code&gt; 找不到接收者。&lt;code&gt;AliasBuf&lt;/code&gt; 则压根不是新类型，&#xA;它&lt;strong&gt;就是&lt;/strong&gt; &lt;code&gt;bytes.Buffer&lt;/code&gt;，方法集自然一模一样。一句话：定义类型造出&lt;strong&gt;新东西&lt;/strong&gt;（新标识、空方法&#xA;集、需自行定义方法），别名只给旧东西&lt;strong&gt;取个新名&lt;/strong&gt;（同一标识、同一方法集）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>5.1 数组与切片</title>
      <link>/zh-cn/part2lang/ch05data/slice/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch05data/slice/</guid>
      <description>&lt;h1 id=&#34;51-数组与切片&#34;&gt;5.1 数组与切片&lt;/h1&gt;&#xA;&lt;p&gt;数组与切片是 Go 里最基础的两种序列类型。它们看起来相似，内存模型却各不相同，理解这点&#xA;能一举解释 append 的种种「惊喜」与切片别名的陷阱。两者共享一个主题：一个小小的&lt;strong&gt;头部&lt;/strong&gt;&#xA;描述一段连续的后备内存。差异全在头部里装了什么、谁拥有那段内存。本节先把布局摆清楚，再从&#xA;「动态数组」这一经典抽象出发，看 Go 的 &lt;code&gt;append&lt;/code&gt; 如何在摊还意义下做到 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，最后落到别名&#xA;与跨语言对照这些日常会撞上的角落。字符串虽与切片同源，但它不可变、自成一类，留到&#xA;&lt;a href=&#34;.././string&#34;&gt;5.2&lt;/a&gt; 专门讨论。&lt;/p&gt;&#xA;&lt;h2 id=&#34;511-三种内存布局&#34;&gt;5.1.1 三种内存布局&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;数组是值。&lt;/strong&gt; &lt;code&gt;[5]int&lt;/code&gt; 就是连续排布的 5 个 &lt;code&gt;int&lt;/code&gt;，长度是类型的一部分：&lt;code&gt;[5]int&lt;/code&gt; 与 &lt;code&gt;[6]int&lt;/code&gt;&#xA;是两个不同的类型。赋值、传参、作为 struct 字段，数组都&lt;strong&gt;整份拷贝&lt;/strong&gt;。正因如此，大数组在 Go 里&#xA;反而少用，传来传去太贵，真要传通常传它的切片或指针。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;切片是对某段底层数组的视图。&lt;/strong&gt; 运行时里它就是一个三字的头部，可对照 &lt;code&gt;runtime/slice.go&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// runtime: 切片的运行时表示（slice.go）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;slice&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;array&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 指向底层数组中本切片的首元素&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 长度：可见元素个数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cap&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 容量：从 array 起到底层数组末尾的元素个数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;len&lt;/code&gt; 是你能索引到的范围，&lt;code&gt;cap&lt;/code&gt; 是「在不重新分配的前提下还能涨到多大」。两者分离正是切片能做&#xA;「视图」的关键：&lt;code&gt;s[1:3]&lt;/code&gt; 只是改头部里的三个字段，不碰底层数组。&lt;/p&gt;</description>
    </item>
    <item>
      <title>5.2 字符串与零拷贝转换</title>
      <link>/zh-cn/part2lang/ch05data/string/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch05data/string/</guid>
      <description>&lt;h1 id=&#34;52-字符串与零拷贝转换&#34;&gt;5.2 字符串与零拷贝转换&lt;/h1&gt;&#xA;&lt;p&gt;字符串与切片同源，运行时里都是「头部 + 别处的内存」（布局见 &lt;a href=&#34;/zh-cn/part2lang/ch05data/slice/#511-%e4%b8%89%e7%a7%8d%e5%86%85%e5%ad%98%e5%b8%83%e5%b1%80&#34;&gt;5.1.1&lt;/a&gt;），&#xA;但它&lt;strong&gt;只读、不可变&lt;/strong&gt;，自成一类。不可变换来共享与免拷贝的好处，也带来一个直接代价：&lt;code&gt;string&lt;/code&gt; 与&#xA;&lt;code&gt;[]byte&lt;/code&gt; 互转默认要拷贝一份字节。本节剖开这份拷贝，看编译器在哪些场景下能把它省掉，再到 Go 1.20&#xA;给出的手动零拷贝工具，以及由此而来的安全契约。&lt;/p&gt;&#xA;&lt;h2 id=&#34;521-字符串与-byte-的转换&#34;&gt;5.2.1 字符串与 []byte 的转换&lt;/h2&gt;&#xA;&lt;p&gt;字符串不可变带来一个直接代价：&lt;code&gt;string&lt;/code&gt; 与 &lt;code&gt;[]byte&lt;/code&gt; 互转默认要&lt;strong&gt;拷贝一份字节&lt;/strong&gt;。原因正是不可变,&#xA;&lt;code&gt;[]byte&lt;/code&gt; 可写，若让它直接指向某个字符串的底层字节，改 &lt;code&gt;[]byte&lt;/code&gt; 就等于改了那个「不可变」的字符串，&#xA;破坏了一切共享假设。所以运行时老老实实 &lt;code&gt;memmove&lt;/code&gt; 一份：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// runtime: []byte → string（string.go，节选）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;slicebytetostring&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tmpBuf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;mallocgc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 分配新内存&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;memmove&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 拷贝字节&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这份拷贝在热路径上可能可观。好在编译器认得几类「转换后字节立刻被读、不可能被改」的模式，把拷贝&#xA;省掉。最常见的两个是用 &lt;code&gt;[]byte&lt;/code&gt; 临时当 map 键查询，与对 &lt;code&gt;[]byte(s)&lt;/code&gt; 直接 range：&lt;/p&gt;</description>
    </item>
    <item>
      <title>5.3 散列表：原理与安全</title>
      <link>/zh-cn/part2lang/ch05data/map/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch05data/map/</guid>
      <description>&lt;h1 id=&#34;53-散列表原理与安全&#34;&gt;5.3 散列表：原理与安全&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Go 的 &lt;code&gt;map&lt;/code&gt; 在 2024 年随 Go 1.24 完成了一次罕见的彻底重写，&#xA;从沿用十四年的经典桶式散列表换成了基于 Swiss Table 的实现。本节先讲清散列表的一般原理&#xA;与攻防，重写的来龙去脉留到 &lt;a href=&#34;.././swisstable&#34;&gt;5.4&lt;/a&gt; 专门交代。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&lt;code&gt;map&lt;/code&gt; 是 Go 仅有的两种泛型容器之一（另一种是 slice）。它由运行时实现、编译器辅助布局，&#xA;本质是一张散列表。读者写下 &lt;code&gt;m[k]&lt;/code&gt; 时，编译器把它翻译成对 &lt;code&gt;runtime.mapaccess&lt;/code&gt;、&#xA;&lt;code&gt;runtime.mapassign&lt;/code&gt; 一族函数的调用，真正的存储、查找、扩容都发生在&#xA;&lt;code&gt;internal/runtime/maps&lt;/code&gt; 包里。这一节先把散列表的一般原理与攻防讲清楚，为下一节落到 Go 自己的&#xA;两代实现（1.0 至 1.23 的经典桶式设计，以及 1.24 起的 Swiss Table 设计）打底。理解了这里的取舍，&#xA;才能看清后者为何值得一次伤筋动骨的重写。&lt;/p&gt;&#xA;&lt;h2 id=&#34;531-散列表的两条路线链地址与开放定址&#34;&gt;5.3.1 散列表的两条路线：链地址与开放定址&lt;/h2&gt;&#xA;&lt;p&gt;散列表要解决的核心矛盾，是把一个几乎无穷大的键空间压进一段有限的连续内存。哈希函数&#xA;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;h&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6944em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 把键映射到 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy=&#34;false&#34;&gt;[&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;mo separator=&#34;true&#34;&gt;,&lt;/mo&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;[0, m)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;mpunct&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的槽位下标，理想情况下一次访存即可定位。但映射不可能单射，&#xA;两个键算出同一下标即是「碰撞」，如何安置碰撞的键，分出了两条历史悠久的路线。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;链地址法&lt;/strong&gt;（chaining）让每个槽位挂一条链表，碰撞的键依次串在链上。它实现简单、删除干净、&#xA;对哈希质量不敏感，代价是每个元素多一个指针、缓存局部性差（链表节点散落在堆上）。设装填因子&#xA;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;/&lt;/mi&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\alpha = n/m&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.0037em;&#34;&gt;α&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;（元素数比槽位数），在均匀哈希假设下，一次不成功查找平均要比较 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo&gt;≈&lt;/mo&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\approx \alpha&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4831em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;≈&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.0037em;&#34;&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个&#xA;元素，成功查找 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo&gt;≈&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;/&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\approx 1 + \alpha/2&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4831em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;≈&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7278em;vertical-align:-0.0833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.0037em;&#34;&gt;α&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;/2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个。链地址法允许 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;mo&gt;&amp;gt;&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\alpha &amp;gt; 1&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5782em;vertical-align:-0.0391em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.0037em;&#34;&gt;α&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，性能随 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\alpha&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.0037em;&#34;&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 线性退化。&lt;/p&gt;</description>
    </item>
    <item>
      <title>5.4 Swiss Table 与 Go 1.24 实现</title>
      <link>/zh-cn/part2lang/ch05data/swisstable/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch05data/swisstable/</guid>
      <description>&lt;h1 id=&#34;54-swiss-table-与-go-124-实现&#34;&gt;5.4 Swiss Table 与 Go 1.24 实现&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././map&#34;&gt;5.3&lt;/a&gt; 讲清了散列表的一般原理与攻防：两条碰撞处理路线，以及哈希洪水的防御。本节落到&#xA;Go 自己的两代实现，先剖开 Swiss Table 这一现代开放定址设计，再看 Go 1.24 如何把它落地、为此&#xA;取代了什么，以及由实现倒推出的两条语言规则，最后把 Go 放进各家散列表的更大图景里对照。&lt;/p&gt;&#xA;&lt;h2 id=&#34;541-swiss-table-设计abseil&#34;&gt;5.4.1 Swiss Table 设计（abseil）&lt;/h2&gt;&#xA;&lt;p&gt;Swiss Table 由 Google 的 abseil 团队提出，Kulukundis 在 CppCon 2017 上公开。它是开放定址法的&#xA;现代化身，核心创新是把「哪个槽是空的、哪个槽该比较」这件事，从逐槽试探变成一次并行操作。&lt;/p&gt;&#xA;&lt;p&gt;它把哈希值劈成两段：高 57 位 &lt;strong&gt;H1&lt;/strong&gt; 决定从哪一组（group）开始探测，低 7 位 &lt;strong&gt;H2&lt;/strong&gt; 作为该键的&#xA;「指纹」。槽位以 8 个为一组排列，每组配一个 8 字节的&lt;strong&gt;控制字&lt;/strong&gt;（control word），其中每个控制&#xA;字节对应组内一个槽：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;空槽    empty    1000 0000   （0x80）&#xA;墓碑    deleted  1111 1110   （0xfe）&#xA;占用    full     0hhh hhhh   （最高位 0，低 7 位即该键的 H2 指纹）&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最高位区分「空/墓碑」与「占用」，占用槽的低 7 位直接存指纹。查找一个键时，先算 H1 定位到起始&#xA;组，再拿 H2 与整组 8 个控制字节并行比较：在支持 SIMD 的机器上，这是一条 &lt;code&gt;PCMPEQB&lt;/code&gt; 指令（16&#xA;字节甚至能比 16 个槽），一步完成本应 8 步的探测。比较结果是一个位掩码，标出哪些槽的指纹匹配；&#xA;逐个核对这些候选槽的真实键即可。指纹只有 7 位，约 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;/&lt;/mi&gt;&lt;mn&gt;128&lt;/mn&gt;&lt;mo&gt;≈&lt;/mo&gt;&lt;mn&gt;0.7&lt;/mn&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;%&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;1/128 \approx 0.7\%&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1/128&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;≈&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.8056em;vertical-align:-0.0556em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0.7%&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的概率假阳性，但既然&#xA;匹配后总要做一次完整键比较，假阳性只是偶尔多比一次，不影响正确性。&lt;/p&gt;</description>
    </item>
    <item>
      <title>6.1 函数调用</title>
      <link>/zh-cn/part2lang/ch06func/func/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch06func/func/</guid>
      <description>&lt;h1 id=&#34;61-函数调用&#34;&gt;6.1 函数调用&lt;/h1&gt;&#xA;&lt;p&gt;函数是 Go 的一等公民，可以赋值、传递、返回、捕获外部变量成为闭包。这背后是两件事的实现：&#xA;函数值（闭包）在内存里如何表示，以及一次函数调用在底层如何传参与返回。后者还藏着 Go 1.17&#xA;一次悄无声息却影响全局的变革：从栈传参改为寄存器传参。这一节把这两件事讲透，并在它们的接缝处&#xA;点出一个关键事实，正是这个事实把「闭包是什么」与「调用怎么发生」焊成同一件事。&lt;/p&gt;&#xA;&lt;h2 id=&#34;611-函数值与闭包&#34;&gt;6.1.1 函数值与闭包&lt;/h2&gt;&#xA;&lt;p&gt;一个 &lt;code&gt;func&lt;/code&gt; 类型的值，本质是一个指向&lt;strong&gt;闭包对象&lt;/strong&gt;的指针。运行时给这个对象起的名字是 &lt;code&gt;funcval&lt;/code&gt;，&#xA;它在 &lt;code&gt;runtime/runtime2.go&lt;/code&gt; 里的定义短得出奇：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// funcval：函数值在运行时的表示（来自 runtime2.go，已加注释）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;funcval&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 后面紧跟变长的、与具体函数相关的数据，即被捕获的环境&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;只有一个固定字段 &lt;code&gt;fn&lt;/code&gt;,函数代码的入口地址。注释里那句「variable-size, fn-specific data here」&#xA;才是闭包的灵魂：紧随 &lt;code&gt;fn&lt;/code&gt; 之后，编译器排布着这个闭包捕获的外部变量。一个不捕获任何变量的&#xA;普通函数，它的 &lt;code&gt;funcval&lt;/code&gt; 只有 &lt;code&gt;fn&lt;/code&gt; 一字，全局唯一、只读；一旦它捕获了外部变量，编译器就要为每次&#xA;求值在堆上构造一个带捕获数据的 &lt;code&gt;funcval&lt;/code&gt;。可以把它画成这样：&lt;/p&gt;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    FV[&amp;#34;func 值&amp;lt;br/&amp;gt;（一个指针）&amp;#34;] --&amp;gt; CO&#xA;    subgraph CO[&amp;#34;闭包对象 funcval&amp;#34;]&#xA;        direction TB&#xA;        FN[&amp;#34;fn：函数代码入口地址&amp;#34;]&#xA;        CAP[&amp;#34;captured：被捕获的变量&amp;lt;br/&amp;gt;（如 &amp;amp;x, &amp;amp;y，按引用）&amp;#34;]&#xA;        FN --- CAP&#xA;    end&#xA;    CALL[&amp;#34;调用 f()&amp;#34;] -.-&amp;gt;|取 fn 字段间接跳转| FN&lt;/pre&gt;&#xA;&lt;p&gt;调用一个函数值，就是取出 &lt;code&gt;fn&lt;/code&gt; 字段间接跳转过去，与接口的方法分发&#xA;（&lt;a href=&#34;../../ch04type/interface&#34;&gt;4.2&lt;/a&gt;）异曲同工。关键细节是&lt;strong&gt;捕获按引用&lt;/strong&gt;：闭包捕获的是变量本身，&#xA;不是它当时的值。下面这段代码里，&lt;code&gt;counter&lt;/code&gt; 返回的两个闭包共享同一个 &lt;code&gt;n&lt;/code&gt;，因为它们捕获的是&#xA;&lt;code&gt;n&lt;/code&gt; 这个变量的地址，而非某个快照：&lt;/p&gt;</description>
    </item>
    <item>
      <title>6.2 延迟语句</title>
      <link>/zh-cn/part2lang/ch06func/defer/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch06func/defer/</guid>
      <description>&lt;h1 id=&#34;62-延迟语句&#34;&gt;6.2 延迟语句&lt;/h1&gt;&#xA;&lt;p&gt;延迟语句 &lt;code&gt;defer&lt;/code&gt; 在最早期的 Go 设计中并不存在，是后来才单独增补的特性，由 Robert Griesemer&#xA;完成语言规范的编写 [Griesemer, 2009]，Ken Thompson 完成最早期的实现 [Thompson, 2009]。&#xA;它的语义读起来很短：被 &lt;code&gt;defer&lt;/code&gt; 的调用会在外层函数返回、发生 panic、或调用 &lt;code&gt;runtime.Goexit&lt;/code&gt;&#xA;时执行。直觉上这像是一个纯编译期特性，类似 C++ 的 RAII（离开作用域自动析构），编译器似乎只需把&#xA;延迟的调用「搬」到函数末尾即可，不该有运行时开销。&lt;/p&gt;&#xA;&lt;p&gt;真实情况要复杂一层。Go 的 &lt;code&gt;defer&lt;/code&gt; 没有与某个资源的作用域绑定，还允许出现在条件、循环里，于是&#xA;它不再是一个静态的作用域概念。一段执行次数取决于运行时的循环，能产生多少个延迟调用在编译期无法&#xA;确定：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;randomDefers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Intn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;golang-design/under-the-hood&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 次数运行时才定&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;正因为存在这种不确定性，&lt;code&gt;defer&lt;/code&gt; 不是免费的午餐。本节先讲清它的语义，再沿着一条贯穿十余年的&#xA;性能演进主线，看它如何从「最慢时被劝退出热路径」一路优化到「几乎零成本」。这条主线的终点，也是&#xA;理解 &lt;a href=&#34;.././panic&#34;&gt;6.3 恐慌与恢复&lt;/a&gt; 的钥匙：在今天的运行时里，正常返回时跑 &lt;code&gt;defer&lt;/code&gt; 与 panic 展开&#xA;时跑 &lt;code&gt;defer&lt;/code&gt;，走的是同一套机器。&lt;/p&gt;&#xA;&lt;h2 id=&#34;621-语义lifo返回与-panic-都触发参数在-defer-处求值&#34;&gt;6.2.1 语义：LIFO、返回与 panic 都触发、参数在 defer 处求值&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;defer&lt;/code&gt; 的可观察语义有三条，记住它们就能解释绝大多数「为什么是这个结果」的疑惑。&lt;/p&gt;</description>
    </item>
    <item>
      <title>6.3 恐慌与恢复内建函数</title>
      <link>/zh-cn/part2lang/ch06func/panic/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch06func/panic/</guid>
      <description>&lt;h1 id=&#34;63-恐慌与恢复内建函数&#34;&gt;6.3 恐慌与恢复内建函数&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;defer&lt;/code&gt;（&lt;a href=&#34;.././defer&#34;&gt;6.2&lt;/a&gt;）已经把「函数退出时要做什么」交代清楚，留下的悬念是「函数被&#xA;异常地打断时又会发生什么」。&lt;code&gt;panic&lt;/code&gt; 与 &lt;code&gt;recover&lt;/code&gt; 这对内建函数回答的正是这个问题：前者中断&#xA;当前的正常控制流并开始沿调用栈向上展开，后者在展开途中把它截住。这一节先讲清它们的语义，&#xA;再落到 go1.26 运行时里 &lt;code&gt;gopanic&lt;/code&gt;、&lt;code&gt;gorecover&lt;/code&gt; 的实现，最后回到一个更要紧的问题：panic&#xA;究竟该被当作什么，以及为什么 Go 没有把它做成 C++ 或 Java 那样的异常机制。&lt;/p&gt;&#xA;&lt;h2 id=&#34;631-语义展开拦截与进程终止&#34;&gt;6.3.1 语义：展开、拦截与进程终止&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;panic&lt;/code&gt; 做三件事，顺序固定。它先停下当前函数余下的语句，转而按后进先出的次序执行当前&#xA;goroutine 已经登记的 &lt;code&gt;defer&lt;/code&gt; 链；每执行一个延迟函数，控制权都短暂回到用户代码，给它一次&#xA;拦截的机会；若整条链走完仍无人拦截，panic 会逐层弹出调用栈，到达 goroutine 栈顶后终止&#xA;&lt;strong&gt;整个进程&lt;/strong&gt;，并打印 panic 值与栈回溯。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;recover&lt;/code&gt; 是拦截的唯一手段，但它的生效条件很窄：只有&lt;strong&gt;直接&lt;/strong&gt;被某个延迟函数调用时才有效。&#xA;直接的意思是，调用 &lt;code&gt;recover&lt;/code&gt; 的那一帧，必须正是发生 panic 的那一帧用 &lt;code&gt;defer&lt;/code&gt; 登记的延迟&#xA;函数本身，多一层嵌套或少一层都不算。一次成功的 &lt;code&gt;recover&lt;/code&gt; 会让 panic 停止展开，返回当时&#xA;传给 &lt;code&gt;panic&lt;/code&gt; 的值，随后控制流如同那个延迟函数正常返回一般，继续往下走。&lt;/p&gt;&#xA;&lt;p&gt;这条「同一调用链、且直接位于延迟函数中」的约束，是后文一切实现细节的根。先用三段代码把它&#xA;落实。第一段，&lt;code&gt;recover&lt;/code&gt; 写错了位置，拦不住：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;C&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;recover&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 拦不住 C 的 panic：B 已经返回，不在展开路径上&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;C&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;panic&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;boom&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;A&lt;/code&gt; 先调用 &lt;code&gt;B&lt;/code&gt;，&lt;code&gt;B&lt;/code&gt; 正常返回、它的 &lt;code&gt;defer&lt;/code&gt; 也早已执行完毕；随后 &lt;code&gt;A&lt;/code&gt; 调用 &lt;code&gt;C&lt;/code&gt;，&lt;code&gt;C&lt;/code&gt; 发生&#xA;panic 时展开的是 &lt;code&gt;C → A&lt;/code&gt; 这条路径，&lt;code&gt;B&lt;/code&gt; 根本不在其上。第二段，把 &lt;code&gt;recover&lt;/code&gt; 放到调用链上游&#xA;的 &lt;code&gt;A&lt;/code&gt; 里，就拦得住：&lt;/p&gt;</description>
    </item>
    <item>
      <title>7.1 问题的演化</title>
      <link>/zh-cn/part2lang/ch07errors/value/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch07errors/value/</guid>
      <description>&lt;h1 id=&#34;71-问题的演化&#34;&gt;7.1 问题的演化&lt;/h1&gt;&#xA;&lt;p&gt;在 &lt;a href=&#34;../../ch06func/panic&#34;&gt;6.3&lt;/a&gt; 里我们已经把那道分水岭画了出来：如何处理错误，是语言设计的&#xA;一处根本抉择。异常派（C++、Java、Python）用 &lt;code&gt;try/catch&lt;/code&gt; 把错误路径从正常逻辑里抽走，主干&#xA;干净，代价是错误路径变得隐式，可能从任意调用点抛出；值派（Go、Rust、以及 C 的返回码传统）&#xA;把错误当作普通返回值显式传递，啰嗦，但每一条错误路径都白纸黑字、无处可藏。Go 选了值派，&#xA;只为「真正异常」保留一个轻量的 &lt;code&gt;panic&lt;/code&gt;/&lt;code&gt;recover&lt;/code&gt;。这一节要讲的，是这个选择落地之后，&#xA;「错误作为值」这一主张本身又经历了怎样的演化：从一个只能打印的字符串，长成一棵可以被程序&#xA;逐层追问「你到底是不是那个错误」的树。&lt;/p&gt;&#xA;&lt;h2 id=&#34;711-错误即值&#34;&gt;7.1.1 错误即值&lt;/h2&gt;&#xA;&lt;p&gt;Go 把错误定义成一个内建接口，全部的约定只有一个方法：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// src/builtin/builtin.go&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;interface&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;任何实现了 &lt;code&gt;Error() string&lt;/code&gt; 的类型，都可以当作 &lt;code&gt;error&lt;/code&gt; 传递。它不是一种特殊的语言构造，&#xA;而是一个再普通不过的接口值，能赋值、能比较、能放进切片、能作为字段、能跨 channel 传递。&#xA;这正是 Rob Pike 在《Errors are values》里反复强调的一句话：错误是值，而值可以被编程。&#xA;程序员不是被动地「捕获」一个从天而降的异常，而是主动地拿一个值去做判断、包装、传播或吞下。&lt;/p&gt;&#xA;&lt;p&gt;这个主张在函数签名上有一处固定的体现：错误总是作为最后一个返回值显式交还给调用方。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 使用 f&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;满屏的 &lt;code&gt;if err != nil&lt;/code&gt; 常被诟病啰嗦，但它恰是值派的代价与收益的同一面：错误既然是值，&#xA;就必须像别的值一样被显式接住，编译器不会替你把它偷偷送到别处。这与 &lt;a href=&#34;../../ch06func/panic&#34;&gt;6.3&lt;/a&gt;&#xA;里 Go 的整体气质一脉相承，显式优于隐式。把错误降格为一个一方法接口，换来的是最大的自由：&#xA;标准库不需要预先规定一套错误等级体系，任何人都能用最贴合自己场景的类型去实现 &lt;code&gt;error&lt;/code&gt;，&#xA;从一个字符串常量，到一个携带行号、文件名、底层系统调用号的结构体。代价是这份自由把责任&#xA;也一并交还给了用户，怎样定义错误、怎样让调用方能可靠地辨认它，都得自己安排。这一节余下的&#xA;篇幅，讲的就是 Go 社区与标准库为这份责任摸索出的答案。&lt;/p&gt;</description>
    </item>
    <item>
      <title>7.2 错误值检查</title>
      <link>/zh-cn/part2lang/ch07errors/inspect/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch07errors/inspect/</guid>
      <description>&lt;h1 id=&#34;72-错误值检查&#34;&gt;7.2 错误值检查&lt;/h1&gt;&#xA;&lt;p&gt;错误一旦在调用链里向上传播，处理它的代码与产生它的代码往往隔着许多层。这就带来一个&#xA;朴素却棘手的问题：拿到一个层层传上来的 &lt;code&gt;error&lt;/code&gt;，调用方如何判断「它到底是不是某个特定的错误」，&#xA;又如何从中取回当初那个携带了上下文的具体错误值？这一节回答的就是这件事，以及 Go 为此&#xA;在 &lt;code&gt;errors&lt;/code&gt; 包里立下的那套约定。&lt;/p&gt;&#xA;&lt;p&gt;最朴素的错误就是一句话。&lt;code&gt;errors.New&lt;/code&gt; 把一个字符串包成 &lt;code&gt;error&lt;/code&gt;，内部只是 &lt;code&gt;errorString&lt;/code&gt;&#xA;这一极简实现：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errors&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errorString&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;              &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errorString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;New&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errorString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;实践中常把它和 &lt;code&gt;fmt.Sprintf&lt;/code&gt; 拼起来用，凑出一句带变量的错误消息。可一旦错误被压成字符串，&#xA;它的「可处理性」便几乎归零：调用方只能拿这句话去和别的字符串比对，无从知道它的来源，&#xA;也无从取回原始的错误值。Go 1.13 在 &lt;code&gt;errors&lt;/code&gt; 包里补上的 &lt;code&gt;Unwrap&lt;/code&gt;、&lt;code&gt;Is&lt;/code&gt;、&lt;code&gt;As&lt;/code&gt;，&#xA;以及 go1.26 新增的泛型 &lt;code&gt;AsType&lt;/code&gt;，要解决的正是这个「错误一旦上浮便不可追问」的困境。&#xA;它们的共同前提，是先把错误串成一条可以回溯的链。&lt;/p&gt;&#xA;&lt;h2 id=&#34;721-错误传播链从单层包装到错误树&#34;&gt;7.2.1 错误传播链：从单层包装到错误树&lt;/h2&gt;&#xA;&lt;p&gt;要让上层能追问下层，错误就不能在传播时丢掉来历。&lt;code&gt;fmt.Errorf&lt;/code&gt; 用 &lt;code&gt;%w&lt;/code&gt; 动词做到这一点：&#xA;它在生成新错误消息的同时，把被包装的原始错误存了下来，使新错误「记得」自己是从谁来的。&#xA;单个 &lt;code&gt;%w&lt;/code&gt; 产出的是一个实现了 &lt;code&gt;Unwrap() error&lt;/code&gt; 的 &lt;code&gt;wrapError&lt;/code&gt;，多个 &lt;code&gt;%w&lt;/code&gt; 则产出实现了&#xA;&lt;code&gt;Unwrap() []error&lt;/code&gt; 的 &lt;code&gt;wrapErrs&lt;/code&gt;：&lt;/p&gt;</description>
    </item>
    <item>
      <title>7.3 错误格式与上下文</title>
      <link>/zh-cn/part2lang/ch07errors/context/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch07errors/context/</guid>
      <description>&lt;h1 id=&#34;73-错误格式与上下文&#34;&gt;7.3 错误格式与上下文&lt;/h1&gt;&#xA;&lt;p&gt;一个好的错误，不只是「出错了」，而是能告诉人：在做什么的时候、因为什么、出了什么错。问题的&#xA;演化（&lt;a href=&#34;.././value&#34;&gt;7.1&lt;/a&gt;）与值检查（&lt;a href=&#34;.././inspect&#34;&gt;7.2&lt;/a&gt;）给了我们传递与检视错误的机制，这一节&#xA;谈机制之上的工程实践，错误的文本应当怎么写、上下文如何沿调用链一层层累积、以及当人写的文字&#xA;不够用时，栈轨迹与结构化日志如何按需补上。贯穿其间的，是 Go 在错误信息上的一个总体倾向：&#xA;人写的语义上下文，优于机器采的栈轨迹。&lt;/p&gt;&#xA;&lt;h2 id=&#34;731-error-string-的约定&#34;&gt;7.3.1 &lt;code&gt;Error() string&lt;/code&gt; 的约定&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;error&lt;/code&gt; 接口只要求一个方法：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;interface&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;围绕这唯一的方法，Go 社区形成了一套不成文却处处可见的风格约定：错误字符串首字母小写、&#xA;结尾不加标点。约定背后的原因很实际。错误极少独立出现，它们几乎总是被层层包装、首尾相接，&#xA;拼成一条更长的字符串。设想三层调用各自补一句上下文，最终用户看到的是：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;read config: open /etc/app.conf: permission denied&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这是三段错误用 &lt;code&gt;: &lt;/code&gt; 拼接而成的一条因果链。若每一段都按句子的写法首字母大写、末尾加句号，&#xA;拼起来便成了「Read config: Open /etc/app.conf: Permission denied.」，一串大写字母与句号&#xA;散落在句子中间，读起来支离破碎。小写、无尾标点的片段，才能顺畅地嵌进任意位置，串成一条&#xA;连贯的链。这条约定细小，却是 Go 错误信息能层层拼接而不失可读的前提。&lt;/p&gt;&#xA;&lt;p&gt;需要澄清一处常见的说法：检查这条约定的并不是 &lt;code&gt;go vet&lt;/code&gt;。历史上它由 &lt;code&gt;golint&lt;/code&gt; 负责，&lt;code&gt;golint&lt;/code&gt;&#xA;归档后，这项检查（编号 ST1005）由 &lt;code&gt;staticcheck&lt;/code&gt; 等第三方工具承担。&lt;code&gt;go vet&lt;/code&gt; 不管错误字符串&#xA;的大小写，但它确实有一个与错误相关的检查，针对 &lt;code&gt;log/slog&lt;/code&gt; 的结构化日志调用（见 7.3.5）。&lt;/p&gt;&#xA;&lt;h2 id=&#34;732-在每一个边界补上上下文&#34;&gt;7.3.2 在每一个边界补上上下文&lt;/h2&gt;&#xA;&lt;p&gt;错误从底层向上传播时，每经过一个有意义的边界，都该补一句「我当时在干什么」。这正是 &lt;code&gt;%w&lt;/code&gt;&#xA;包装（&lt;a href=&#34;.././inspect&#34;&gt;7.2&lt;/a&gt;）的首要用途，也是 Go 错误处理的核心实践。下面是一段典型的&#xA;配置加载代码，它在两个不同的失败点各自标注了当时的意图：&lt;/p&gt;</description>
    </item>
    <item>
      <title>7.4 错误语义</title>
      <link>/zh-cn/part2lang/ch07errors/semantics/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch07errors/semantics/</guid>
      <description>&lt;h1 id=&#34;74-错误语义&#34;&gt;7.4 错误语义&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././inspect&#34;&gt;7.2&lt;/a&gt; 与 &lt;a href=&#34;.././context&#34;&gt;7.3&lt;/a&gt; 站在错误的调用方一侧，讲清了拿到一个 &lt;code&gt;error&lt;/code&gt;&#xA;之后如何沿链检查、如何叠加上下文。这一节把视角调到另一侧，站在库作者一侧问一个先于检查的问题：&#xA;当一个包要把失败暴露给外界时，它应当把错误做成什么&lt;strong&gt;形式&lt;/strong&gt;？是导出一个可比较的值，&#xA;还是导出一个带字段的类型，抑或什么都不导出？&lt;/p&gt;&#xA;&lt;p&gt;这并非纯粹的风格之争。错误的形式一旦发布，就成了包的公开契约的一部分，和函数签名一样难以更改。&#xA;调用方会照着这个形式写下分支逻辑，而这些分支逻辑反过来把调用方与库&lt;strong&gt;耦合&lt;/strong&gt;在了一起。&#xA;不同的形式，承诺给契约的东西不同，因而日后能改动的自由度也不同。本节把社区里成型的几种做法&#xA;摆在一起，按「向 API 契约承诺了什么」这条线索逐一称量它们的代价，最后给出一个可操作的取舍次序。&lt;/p&gt;&#xA;&lt;h2 id=&#34;741-哨兵错误向契约承诺一个值&#34;&gt;7.4.1 哨兵错误：向契约承诺一个值&lt;/h2&gt;&#xA;&lt;p&gt;最直接的做法是导出一个预先创建好的错误值，让调用方拿它做相等比较。标准库里这样的例子俯拾皆是：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;io&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// EOF 是 Read 在没有更多输入时返回的错误。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;EOF&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;New&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;EOF&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ErrNoRows 在 QueryRow 没有命中任何行时由 Row.Scan 返回。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ErrNoRows&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;New&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;sql: no rows in result set&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;调用方据此分支：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;row&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;QueryRow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;select ...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;row&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Scan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Is&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ErrNoRows&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 查无此行，走业务上的「不存在」分支&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这类预置的错误值称为&lt;strong&gt;哨兵错误&lt;/strong&gt;（sentinel error）。它简单到近乎透明，是表达「成功之外的一种确定结局」&#xA;的最省力手段。&lt;code&gt;io.EOF&lt;/code&gt; 之所以不算真正的「错误」而更像一个信号，正因为它表达的是「正常读到了流的末端」。&lt;/p&gt;</description>
    </item>
    <item>
      <title>7.5 错误处理的未来</title>
      <link>/zh-cn/part2lang/ch07errors/future/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch07errors/future/</guid>
      <description>&lt;h1 id=&#34;75-错误处理的未来&#34;&gt;7.5 错误处理的未来&lt;/h1&gt;&#xA;&lt;p&gt;前几节里我们见过 &lt;code&gt;error&lt;/code&gt; 接口的朴素、&lt;code&gt;%w&lt;/code&gt; 包装与错误链的检视。读者读到这里，心里多半积着&#xA;一个没有说出口的疑问：&lt;code&gt;if err != nil&lt;/code&gt; 这三行模板，年复一年地铺满每个 Go 程序，难道就没有&#xA;办法收掉吗？这个疑问并不孤单。从 2018 年的 go2 草案到 2025 年的一纸结论，Go 团队为它前后&#xA;推敲了七年，提出过 &lt;code&gt;check&lt;/code&gt;/&lt;code&gt;handle&lt;/code&gt;、&lt;code&gt;try&lt;/code&gt;、&lt;code&gt;?&lt;/code&gt; 等多套语法，又把它们一一收回。本节把这条&#xA;没有走通的语法之路，与另一条悄然走通的库演进之路，并排摆出来，并交代 Go 团队最终给出的&#xA;判断：在可见的将来，错误处理不会迎来专门的语法。&lt;/p&gt;&#xA;&lt;p&gt;理解这个结局，比记住任何一套被否决的语法都重要。它不是搁置，而是一次有据可依的「不做」，&#xA;背后是 Go 对「显式优于简洁」的一次郑重重申。&lt;/p&gt;&#xA;&lt;h2 id=&#34;751-被否决的语法尝试&#34;&gt;7.5.1 被否决的语法尝试&lt;/h2&gt;&#xA;&lt;h3 id=&#34;check--handle2018-年的-go2-草案&#34;&gt;check / handle：2018 年的 go2 草案&lt;/h3&gt;&#xA;&lt;p&gt;错误处理语法化的第一次正式尝试，是 2018 年随「Go 2」蓝图公布的 &lt;code&gt;check&lt;/code&gt;/&lt;code&gt;handle&lt;/code&gt; 草案。&#xA;它引入两个关键字：&lt;code&gt;check&lt;/code&gt; 是一个表达式，对返回 &lt;code&gt;(T, error)&lt;/code&gt; 的调用求值，错误为 &lt;code&gt;nil&lt;/code&gt; 时&#xA;取出 &lt;code&gt;T&lt;/code&gt;，否则把控制权交给最近的 &lt;code&gt;handle&lt;/code&gt; 块；&lt;code&gt;handle&lt;/code&gt; 是一个语句，声明当前函数里检查&#xA;失败时的善后逻辑。一个典型的文件拷贝在草案下写成这样：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;CopyFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dst&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;handle&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Errorf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;copy %s %s: %v&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dst&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;check&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 失败则跳到上面的 handle&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;check&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dst&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;handle&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                 &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// handle 可叠加，后声明的先生效&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Remove&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dst&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;check&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;io&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;check&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;它的野心在 &lt;code&gt;handle&lt;/code&gt; 链：多个 &lt;code&gt;handle&lt;/code&gt; 按声明逆序生效，让人联想到 &lt;code&gt;defer&lt;/code&gt; 的栈式语义，能把&#xA;「出错时回滚」的清理逻辑沿调用顺序层层铺开。代价也正在于此。读者要读懂某一行 &lt;code&gt;check&lt;/code&gt;&#xA;失败后会发生什么，得在脑子里维护一个 &lt;code&gt;handle&lt;/code&gt; 栈，与 &lt;code&gt;defer&lt;/code&gt; 栈彼此独立又彼此纠缠。社区&#xA;普遍反馈它「为省下 &lt;code&gt;if&lt;/code&gt; 而引入了一套新的、需要单独学习的控制流」，复杂度未必比它消除的更低。&#xA;草案最终被判定过于复杂而未进入提案阶段。&lt;/p&gt;</description>
    </item>
    <item>
      <title>8.1 泛型设计的演进</title>
      <link>/zh-cn/part2lang/ch08generics/history/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch08generics/history/</guid>
      <description>&lt;h1 id=&#34;81-泛型设计的演进&#34;&gt;8.1 泛型设计的演进&lt;/h1&gt;&#xA;&lt;p&gt;泛型是 Go 等待最久、争论最烈、也最能体现其设计哲学的一项特性。从 2009 年开源到 2022 年的&#xA;Go 1.18 才落地，这十三年的迟疑与最终的取舍，本身就是一堂语言设计课。这一节回答三个问题：&#xA;Go 为何久久不加泛型、最终怎样加的、它在底层又是如何实现的。后者尤其关键，因为 Go 的实现&#xA;没有照搬任何一家成例，而是走了一条独特的中间道路，这条道路才是它对那个十三年难题的真正回答。&#xA;设计本身一路如何演变（合约、类型集、多轮语法提案），留待 &lt;a href=&#34;.././future&#34;&gt;8.4&lt;/a&gt; 细说，本节只取&#xA;其骨架。&lt;/p&gt;&#xA;&lt;h2 id=&#34;811-久拖不决泛型的两难&#34;&gt;8.1.1 久拖不决：泛型的两难&lt;/h2&gt;&#xA;&lt;p&gt;缺了泛型，写一段对多种类型行为相同的代码会变成什么样？最直接的办法是为每个类型各抄一份：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;MaxInt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;MaxFloat64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;float64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;float64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;MaxUintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ……每多一个类型，就多抄一份逻辑相同的代码&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;逻辑一字不差，只有类型不同。第二条路是用 &lt;code&gt;interface{}&lt;/code&gt; 把类型差异抹掉，让一份代码服务所有类型：&lt;/p&gt;</description>
    </item>
    <item>
      <title>8.2 基于合约的泛型</title>
      <link>/zh-cn/part2lang/ch08generics/contracts/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch08generics/contracts/</guid>
      <description>&lt;h1 id=&#34;82-基于合约的泛型&#34;&gt;8.2 基于合约的泛型&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;本节内容另有一份配套的线上演讲：&lt;a href=&#34;https://www.youtube.com/watch?v=E16Y6bI2S08&#34;&gt;YouTube 在线&lt;/a&gt;、&#xA;&lt;a href=&#34;https://changkun.de/s/go2generics/&#34;&gt;Google Slides 讲稿&lt;/a&gt;。讲稿录于合约提案尚在讨论的 2019 年，&#xA;与本节互为印证。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././history&#34;&gt;8.1&lt;/a&gt; 用一句话带过了「先有合约、后转向接口即约束」这段演进。这一节把那句话展开：&#xA;合约究竟长什么样、它有多强的表达力、又为何最终被放弃。这段被否决的设计不是历史的废料，它解释了&#xA;今天 &lt;code&gt;[T Ordered]&lt;/code&gt; 语法的由来，也示范了 Go 设计中一个反复出现的动作,先做出一个表达力很强但&#xA;偏复杂的方案，再回头追问「这件事能不能用已有的概念表达」，逼着复杂度去赚取自己存在的资格。&lt;/p&gt;&#xA;&lt;p&gt;本节展示的所有 &lt;code&gt;contract&lt;/code&gt; 语法都已废弃，仅出现在 2018 年至 2020 年的 go2draft 草案里，&#xA;&lt;strong&gt;任何一个 Go 编译器都从未接受过它们&lt;/strong&gt;。下文凡是合约写法，都按其在草案中的原貌呈现，并明确标注&#xA;「已废弃」，以免与今天可用的语法混淆。&lt;/p&gt;&#xA;&lt;h2 id=&#34;821-约束想解决什么&#34;&gt;8.2.1 约束想解决什么&lt;/h2&gt;&#xA;&lt;p&gt;泛型函数迟早要回答一个问题：类型参数 &lt;code&gt;T&lt;/code&gt; 能做哪些操作？写一个求最大值的 &lt;code&gt;Max&lt;/code&gt;，函数体里要用到&#xA;比较运算符 &lt;code&gt;&amp;lt;&lt;/code&gt;，可 &lt;code&gt;T&lt;/code&gt; 若被实例化成一个不支持比较的类型（比如某个 struct 或 slice），这段代码就&#xA;不成立。于是泛型机制必须提供一种手段，让作者&lt;strong&gt;声明&lt;/strong&gt; &lt;code&gt;T&lt;/code&gt; 必须支持哪些操作，并让编译器据此在&#xA;实例化处做检查。这就是「约束」（constraint）。&lt;/p&gt;&#xA;&lt;p&gt;约束不是可有可无的修饰。没有它，泛型只剩两条退路：要么像 C++ 早期模板那样&lt;strong&gt;完全不约束&lt;/strong&gt;，类型&#xA;错误推迟到实例化时才以一长串难懂的报错暴露出来；要么像 &lt;code&gt;interface{}&lt;/code&gt; 那样&lt;strong&gt;放弃静态信息&lt;/strong&gt;，靠&#xA;运行时断言兜底，把类型安全和性能一并丢掉（&lt;a href=&#34;.././history&#34;&gt;8.1&lt;/a&gt; 的两难）。约束的价值，正是把&#xA;「&lt;code&gt;T&lt;/code&gt; 得能做什么」这件事提前到声明处讲清楚，让错误在调用点就被拦下，也让编译器有依据生成正确的&#xA;代码。问题只剩一个：用什么语法把这组要求写出来。&lt;/p&gt;&#xA;&lt;h2 id=&#34;822-合约一段不会执行的代码&#34;&gt;8.2.2 合约：一段不会执行的代码&lt;/h2&gt;&#xA;&lt;p&gt;2018 年 go2draft 给出的第一版答案，是引入一个新关键字 &lt;code&gt;contract&lt;/code&gt;。合约形如一个函数：它带一组&#xA;类型参数，函数体里&lt;strong&gt;罗列&lt;/strong&gt;这些类型必须满足的条件。以 &lt;code&gt;Max&lt;/code&gt; 为例，最朴素的设想是直接在体内写出&#xA;要用到的运算符（这是草案探讨过的更早一版写法）：&lt;/p&gt;</description>
    </item>
    <item>
      <title>8.3 类型检查技术</title>
      <link>/zh-cn/part2lang/ch08generics/checker/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch08generics/checker/</guid>
      <description>&lt;h1 id=&#34;83-类型检查技术&#34;&gt;8.3 类型检查技术&lt;/h1&gt;&#xA;&lt;p&gt;泛型给类型检查器出了远比从前难的题。从前的判断只有一种形状：「这个值是不是这个类型」。&#xA;有了类型参数，检查器还得回答三个新问题：一个类型实参是否满足约束、&lt;code&gt;a + b&lt;/code&gt; 这样的运算在&#xA;未知类型上是否成立、以及调用 &lt;code&gt;Max(3, 5)&lt;/code&gt; 时那个没写出来的 &lt;code&gt;T&lt;/code&gt; 到底是什么。这三件事分别对应&#xA;本节的三个主题：类型集、核心类型、类型推断。它们是 &lt;a href=&#34;.././history&#34;&gt;8.1&lt;/a&gt; 那套设计落到编译期的&#xA;具体技术，也是 &lt;a href=&#34;/zh-cn/part5toolchain/ch15compile/&#34;&gt;15 编译器&lt;/a&gt; 前端的前奏。&lt;/p&gt;&#xA;&lt;p&gt;贯穿本节的标尺只有一句：泛型让类型系统的复杂度陡增，而 Go 把这份复杂度尽量关在编译器内部，&#xA;不让它溢出到用户面前。读完应当能说清，这份「关」是靠哪几样技术做到的。三个新问题与三样技术&#xA;一一对应，构成本节的骨架：&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    Q1[&amp;#34;实参是否满足约束？&amp;#34;] --&amp;gt; T1[&amp;#34;类型集&amp;lt;br/&amp;gt;type set：约束即一个类型的集合&amp;#34;]&#xA;    Q2[&amp;#34;a &amp;#43; b、range s 在未知类型上是否成立？&amp;#34;] --&amp;gt; T2[&amp;#34;核心类型&amp;lt;br/&amp;gt;core / common underlying type&amp;#34;]&#xA;    Q3[&amp;#34;Max(3, 5) 里的 T 是什么？&amp;#34;] --&amp;gt; T3[&amp;#34;类型推断&amp;lt;br/&amp;gt;函数实参合一 &amp;#43; 约束类型推断&amp;#34;]&#xA;    T1 --&amp;gt; IMPL[&amp;#34;types2：编译器内置、懂泛型的类型检查器&amp;#34;]&#xA;    T2 --&amp;gt; IMPL&#xA;    T3 --&amp;gt; IMPL&lt;/pre&gt;&#xA;&lt;h2 id=&#34;831-类型集约束是一个类型的集合&#34;&gt;8.3.1 类型集：约束是一个类型的集合&lt;/h2&gt;&#xA;&lt;p&gt;约束的核心概念是&lt;strong&gt;类型集&lt;/strong&gt;（type set）。一个普通接口描述「有哪些方法」，约束接口则进一步描述&#xA;「&lt;strong&gt;哪些类型属于我&lt;/strong&gt;」。把接口从方法集推广为类型集（&lt;a href=&#34;.././history&#34;&gt;8.1.2&lt;/a&gt;），是让既有的接口语法&#xA;直接承载约束的关键一步。&lt;/p&gt;&#xA;&lt;p&gt;类型集由&lt;strong&gt;类型项&lt;/strong&gt;（type term）的并集构成。spec 把类型项的语义定得很干净，编译器&#xA;&lt;code&gt;cmd/compile/internal/types2&lt;/code&gt; 里的 &lt;code&gt;term&lt;/code&gt; 结构原样照搬了这套集合论记号：&lt;/p&gt;</description>
    </item>
    <item>
      <title>8.4 泛型的未来</title>
      <link>/zh-cn/part2lang/ch08generics/future/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part2lang/ch08generics/future/</guid>
      <description>&lt;h1 id=&#34;84-泛型的未来&#34;&gt;8.4 泛型的未来&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;泛型自 1.18 落地至今已逾四年，本节不再停留在「将会怎样」的猜想，&#xA;而是回过头清点：哪些设想兑现了、哪些被有意搁置、以及那个贯穿始终的张力（抽象的代价）今天&#xA;走到了哪一步。本节作者早年曾就 Go 2 泛型做过一次公开演讲（&lt;a href=&#34;https://www.youtube.com/watch?v=E16Y6bI2S08&#34;&gt;YouTube&lt;/a&gt;、&#xA;&lt;a href=&#34;https://changkun.de/s/go2generics/&#34;&gt;讲稿&lt;/a&gt;），如今多数预言已可对照现实检验，下文一并交代。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././history&#34;&gt;8.1&lt;/a&gt; 讲过泛型从合约到「接口即约束」的十三年演进，&lt;a href=&#34;.././checker&#34;&gt;8.3&lt;/a&gt; 讲过类型检查器&#xA;如何消化类型参数与约束。落地之后的故事则是另一条线：一项语言特性发布只是起点，它在标准库里&#xA;长出哪些惯用法、社区在使用中撞见哪些边界、团队又据此添了什么、按下了什么，这些才决定它最终&#xA;的形状。Go 团队对泛型一向自陈谨慎：先发布最小可用的一版，看真实需求浮现，再小步添加。本节&#xA;就沿着「已落地、仍缺席、核心张力、演进哲学」四条来盘点这份谨慎换来了什么。&lt;/p&gt;&#xA;&lt;h2 id=&#34;841-自-118-以来落地了什么&#34;&gt;8.4.1 自 1.18 以来落地了什么&lt;/h2&gt;&#xA;&lt;p&gt;泛型在 1.18 只交付了语言层的类型参数与约束。真正让它进入日常代码的，是随后几个版本围绕它&#xA;长出的标准库与惯用法。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;slices&lt;/code&gt;、&lt;code&gt;maps&lt;/code&gt;、&lt;code&gt;cmp&lt;/code&gt;：泛型标准库（1.21）。&lt;/strong&gt; 在泛型之前，「对任意切片排序、查找、去重」&#xA;要么靠 &lt;code&gt;sort.Slice&lt;/code&gt; 加闭包、要么靠 &lt;code&gt;interface{}&lt;/code&gt; 加反射，两条路都既不安全也不快。1.21 把这些&#xA;操作收进三个泛型包。&lt;code&gt;cmp&lt;/code&gt; 给出有序类型的约束与比较原语：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// cmp 包：把「可比较大小」抽象成一个约束（速写自 src/cmp）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Ordered&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;interface&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int16&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint16&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;float32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;float64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Less&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;T&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Ordered&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;](&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;y&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;T&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;isNaN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;isNaN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;y&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;y&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意约束里每一项都带波浪号 &lt;code&gt;~&lt;/code&gt;，意为「底层类型为此」而非「恰为此」，于是用户自定义的&#xA;&lt;code&gt;type Celsius float64&lt;/code&gt; 也落在 &lt;code&gt;Ordered&lt;/code&gt; 之内。&lt;code&gt;slices&lt;/code&gt; 与 &lt;code&gt;maps&lt;/code&gt; 则建在这套约束之上，给出&#xA;类型安全、无反射的常用操作：&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.1 调度问题与 GMP 模型</title>
      <link>/zh-cn/part3concurrency/ch09sched/model/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/model/</guid>
      <description>&lt;h1 id=&#34;91-调度问题与-gmp-模型&#34;&gt;9.1 调度问题与 GMP 模型&lt;/h1&gt;&#xA;&lt;p&gt;写下 &lt;code&gt;go f()&lt;/code&gt;，一个 goroutine 就开始运行了。这行代码背后，是 Go 运行时最精巧的一台机器：&#xA;调度器。它要回答一个并不简单的问题：成千上万个 goroutine，如何在为数不多的几个 CPU 核心上&#xA;轮转，既跑得快，又让用户几乎察觉不到它的存在。本节先把它要解决的问题、整体骨架，以及它在&#xA;并发运行时这个大家族里的位置交代清楚，后面各节再深入每个部件。&lt;/p&gt;&#xA;&lt;h2 id=&#34;911-三种线程模型与一段历史&#34;&gt;9.1.1 三种线程模型，与一段历史&lt;/h2&gt;&#xA;&lt;p&gt;把&amp;quot;并发任务&amp;quot;映射到&amp;quot;CPU 执行资源&amp;quot;上，历史上有三种模型，它们的取舍决定了一切。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;1:1（内核线程）&lt;/strong&gt;：每个用户线程对应一个操作系统线程。真并行、阻塞系统调用由内核透明&#xA;处理、实现简单；代价是每次创建与切换都要陷入内核，每个线程还要预留以兆字节计的栈。&#xA;Linux 的 NPTL、现代 Windows、Java 的平台线程都走这条路。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;N:1（纯用户线程，即早期的「绿色线程」）&lt;/strong&gt;：多个用户线程挤在一个操作系统线程上。切换&#xA;极廉价、栈极小；但&lt;strong&gt;用不上多核&lt;/strong&gt;，而且&lt;strong&gt;一次阻塞的系统调用会卡住全部线程&lt;/strong&gt;,这是它的死穴。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;M:N（混合 / 两级）&lt;/strong&gt;：把 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;M&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;M&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;M&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个用户线程多路复用到 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个内核线程上。既廉价又能并行，&#xA;代价是需要&lt;strong&gt;用户态与内核态两个调度器协作&lt;/strong&gt;,这正是复杂度的根源。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;历史在这里拐过一个有意思的弯。1990 年代，M:N 一度被寄予厚望，最有影响的方案是&#xA;Anderson 等人的&lt;strong&gt;调度器激活&lt;/strong&gt;（scheduler activations，SOSP 1991）：让内核在阻塞、就绪等&#xA;时刻&amp;quot;通知&amp;quot;用户态调度器，使两个调度器协同。但工业界最终大多退回了 1:1。Drepper 与 Molnar&#xA;为 Linux 设计 NPTL 时（2005）把理由写得很直白：M:N 需要两个调度器，若不协同则性能受损，&#xA;而要让它们协同所需引入内核基础设施的成本与维护代价都太高，&amp;ldquo;不符合 Linux 内核的理念&amp;rdquo;。&#xA;于是 Linux 选了 1:1。&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    subgraph OS[操作系统]&#xA;      M1[M1 线程]&#xA;      M2[M2 线程]&#xA;    end&#xA;    subgraph RT[Go 运行时]&#xA;      direction TB&#xA;      P1[&amp;#34;P1 · 本地队列 G G G&amp;#34;]&#xA;      P2[&amp;#34;P2 · 本地队列 G G&amp;#34;]&#xA;      GQ[&amp;#34;全局队列 G G G G&amp;#34;]&#xA;    end&#xA;    M1 --&amp;gt; P1&#xA;    M2 --&amp;gt; P2&#xA;    P2 -. 空闲时窃取一半 .-&amp;gt; P1&#xA;    P1 -. 队列溢出 .-&amp;gt; GQ&#xA;    GQ -. 定期补充 .-&amp;gt; P2&lt;/pre&gt;&#xA;&lt;p&gt;Go 偏偏又走回了 M:N。它之所以能避开当年的坑，靠的是三件事，而这三件事都握在运行时自己手里：&#xA;goroutine 的栈很小且可增长（起步几 KB），创建与切换都廉价；运行时掌握所有会阻塞的点，&#xA;能在阻塞前把执行权让出；网络 I/O 由内置的网络轮询器（&lt;a href=&#34;.././poller&#34;&gt;9.9&lt;/a&gt;）接管，阻塞的&#xA;goroutine 会被挂起而不占用线程。当年杀死 N:1 的&amp;quot;阻塞系统调用&amp;quot;难题，Go 是在运行时内部解决的，&#xA;而不是去求内核提供激活机制。&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.2 工作窃取式调度</title>
      <link>/zh-cn/part3concurrency/ch09sched/steal/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/steal/</guid>
      <description>&lt;h1 id=&#34;92-工作窃取式调度&#34;&gt;9.2 工作窃取式调度&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././model&#34;&gt;9.1&lt;/a&gt; 留下一个问题：每个 P 各有一条本地队列，活儿难免分布不均，有的 P 忙不过来，&#xA;有的 P 无所事事。如何在不引入中心瓶颈的前提下把负载摊匀，是并发调度的核心难题。Go 的答案，&#xA;是一个有着三十年理论积累、并在整个工业界反复出现的设计：工作窃取（work stealing）。&lt;/p&gt;&#xA;&lt;p&gt;本节会比别处走得更深一些：先讲清 Go 怎么做，再追到它背后的调度理论（为什么它「可证明地好」），&#xA;然后横看它在 Cilk、Java、Rust 等系统里的不同化身，最后停在仍然开放的问题上。&lt;/p&gt;&#xA;&lt;h2 id=&#34;921-共享还是窃取&#34;&gt;9.2.1 共享还是窃取&lt;/h2&gt;&#xA;&lt;p&gt;把任务在处理器间挪动，历史上有两种范式。&lt;strong&gt;工作共享&lt;/strong&gt;（work sharing）：谁生出新任务，就主动把&#xA;一部分推给空闲的处理器。&lt;strong&gt;工作窃取&lt;/strong&gt;（work stealing）：空闲的处理器自己动手，去别人那里把任务&#xA;偷过来。差别在迁移的频率。工作共享只要有新任务就可能触发迁移；工作窃取只在某个处理器真的&#xA;没活干时才迁移，当所有处理器都忙，窃取者找不到下手的机会，迁移自然停止。&lt;strong&gt;负载越重，&#xA;工作窃取反而越安静&lt;/strong&gt;，这是它相对工作共享的根本优势，也有严格的通信量界限支撑（见 9.2.4）。&lt;/p&gt;&#xA;&lt;h2 id=&#34;922-go-的找活儿顺序&#34;&gt;9.2.2 Go 的找活儿顺序&lt;/h2&gt;&#xA;&lt;p&gt;一个绑定了 P 的 M 运行完手头的 G 后，并不直接去窃取，而是按一条由近及远、由廉价到昂贵的&#xA;顺序搜索（运行时的 &lt;code&gt;findRunnable&lt;/code&gt;）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// findRunnable：M 找下一个可运行的 G（伪代码）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;findRunnable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;schedtick&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;%&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;61&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sched&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runq&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;empty&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 1. 每 61 次先看全局，保证公平&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;globrunqget&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;runqget&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 2. 本地队列（含 runnext）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;globrunqget&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 3. 全局队列&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;netpoll&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 4. 网络轮询器&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;stealWork&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 5. 从其他 P 窃取一半&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;stopm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                                            &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 6. 实在没有：自旋或休眠&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;三个让窃取高效的细节。&lt;strong&gt;本地队列有界&lt;/strong&gt;：每个 P 是一个定长 256 的环形缓冲，绝大多数入队出队&#xA;无锁；放满时把一半搬到全局队列（&lt;code&gt;runqputslow&lt;/code&gt;）兜底。&lt;strong&gt;窃取目标随机且打散&lt;/strong&gt;：若所有空闲 P 都&#xA;从同一起点按固定顺序去偷，会一窝蜂挤向同一目标。Go 让每个窃取者以随机起点加上一个与 P 总数&#xA;&lt;strong&gt;互质&lt;/strong&gt;的随机步长，走出覆盖全部 P 的伪随机排列，互质保证不重不漏，随机化避免羊群效应。&#xA;&lt;strong&gt;自旋线程&lt;/strong&gt;：允许少量 M 处于自旋态（上限 &lt;code&gt;GOMAXPROCS&lt;/code&gt;，计于 &lt;code&gt;sched.nmspinning&lt;/code&gt;）主动找活儿&#xA;而不立刻休眠，新就绪的 G 能被迅速接住，免去频繁的线程休眠唤醒。&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.3 MPG 模型与并发调度单元</title>
      <link>/zh-cn/part3concurrency/ch09sched/mpg/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/mpg/</guid>
      <description>&lt;h1 id=&#34;93-mpg-模型与并发调度单元&#34;&gt;9.3 MPG 模型与并发调度单元&lt;/h1&gt;&#xA;&lt;p&gt;调度器要回答的第一个问题不是「怎么调度」，而是「调度什么」。Go 把这个被调度的对象叫&#xA;goroutine，并用一套 M、P、G 三元组承载它。在动手分析调度算法（&lt;a href=&#34;.././schedule&#34;&gt;9.4&lt;/a&gt; 起）之前，&#xA;本节先把这三个调度单元安顿好：goroutine 在计算机科学谱系里究竟是什么、它的运行现场如何编码、&#xA;为何调度本身要在一个特殊的 g0 上进行、一个 goroutine 的一生会经历哪些状态，以及承载它们的&#xA;工作线程 M 如何被暂止与复始。读懂这几样东西，后面的调度算法就只是「在这些单元之间搬运 G」。&lt;/p&gt;&#xA;&lt;p&gt;为避免落回逐字段翻译源码的窠臼，下文给出的结构体都是裁剪后的速写：只保留与设计相关的字段，&#xA;并在注释里说明它为何存在。完整定义可对照 &lt;code&gt;runtime/runtime2.go&lt;/code&gt; 与 &lt;code&gt;runtime/proc.go&lt;/code&gt;（本节对标&#xA;go1.26）。&lt;/p&gt;&#xA;&lt;h2 id=&#34;931-goroutine-是什么有栈协程&#34;&gt;9.3.1 goroutine 是什么：有栈协程&lt;/h2&gt;&#xA;&lt;p&gt;goroutine 常被一句「轻量级线程」带过，这话不错，却遮住了它真正的身世。把它放回计算机科学的&#xA;谱系里，goroutine 是一个&lt;strong&gt;有栈协程&lt;/strong&gt;（stackful coroutine）。&lt;/p&gt;&#xA;&lt;p&gt;协程的概念可上溯到 Conway 1963 年提出 coroutine 一词时的设想：两段子程序互为对方的调用者，&#xA;能在中途交出控制权、又能从交出处原样恢复，而非像普通函数那样必须执行到底才返回。Moura 与&#xA;Ierusalimschy 在 2009 年给协程做了一份清晰的分类，两条正交的轴至今仍是讨论协程的基本坐标：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;对称（symmetric）与非对称（asymmetric）&lt;/strong&gt;：对称协程之间地位平等，靠一个统一的 transfer&#xA;原语彼此跳转；非对称协程则有明确的「调用者」与「被调者」，被调者只能让出（yield）回它的&#xA;调用者。Go 的用户看不到 yield，但运行时内部 goroutine 与调度循环之间正是非对称的让出关系。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;有栈（stackful）与无栈（stackless）&lt;/strong&gt;：有栈协程拥有自己独立的调用栈，因此可以在&lt;strong&gt;任意&#xA;深度的嵌套调用中&lt;/strong&gt;挂起，挂起点不必是协程入口函数本身；无栈协程则没有独立栈，只能在顶层&#xA;函数里挂起，深层调用想挂起就得把整条调用链改写成状态机。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;goroutine 落在「非对称、有栈」这一格。有栈这一点尤其关键，它意味着一个 goroutine 可以在&#xA;任意函数、任意调用深度处被挂起（无论是主动 &lt;code&gt;&amp;lt;-ch&lt;/code&gt; 阻塞，还是被调度器抢占），挂起时整条 Go&#xA;调用栈连同其上的局部变量原封不动地保留，复始时从断点继续。用更理论的话说，一次挂起就是对&#xA;当前执行的一次&lt;strong&gt;一次性定界续延&lt;/strong&gt;（one-shot delimited continuation）的捕获，而这份续延的物理&#xA;形态，就是下一节要讲的 &lt;code&gt;gobuf&lt;/code&gt;：一组保存下来的寄存器（SP、PC 等），足以让执行从断点恢复。&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.4 调度循环</title>
      <link>/zh-cn/part3concurrency/ch09sched/schedule/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/schedule/</guid>
      <description>&lt;h1 id=&#34;94-调度循环&#34;&gt;9.4 调度循环&lt;/h1&gt;&#xA;&lt;p&gt;前面几节备齐了材料：知道了 G、M、P 是什么（&lt;a href=&#34;.././mpg&#34;&gt;9.3&lt;/a&gt;），知道了一个 M 怎样找活儿&#xA;（&lt;a href=&#34;.././steal&#34;&gt;9.2&lt;/a&gt;）。这一节把它们真正转起来，看调度循环如何在一个线程上一刻不停地挑选并&#xA;运行 goroutine，以及它如何在「让单个 goroutine 跑得久一点」（吞吐与局部性）与「别让任何&#xA;goroutine 饿死」（公平）之间拿捏分寸。&lt;/p&gt;&#xA;&lt;p&gt;下文的代码一律是&lt;strong&gt;裁剪后的速写&lt;/strong&gt;，只保留与设计相关的骨架，去掉了 GC、tracing、profiling、&#xA;锁定线程等旁支。完整定义可对照 &lt;code&gt;runtime/proc.go&lt;/code&gt;，下文涉及的版本均为 go1.26。&lt;/p&gt;&#xA;&lt;h2 id=&#34;941-一个永不返回的循环&#34;&gt;9.4.1 一个永不返回的循环&lt;/h2&gt;&#xA;&lt;p&gt;Go 的调度是&lt;strong&gt;协作式、运行到让出&lt;/strong&gt;（run-to-yield）的：一个 goroutine 一旦被选中，就一直跑到它&#xA;主动让出、阻塞、或被抢占（&lt;a href=&#34;.././preemption&#34;&gt;9.7&lt;/a&gt;）为止，而不是像内核那样被时钟中断按固定&#xA;时间片切走。每个工作线程从 &lt;code&gt;mstart&lt;/code&gt; 启动后，最终进入调度循环 &lt;code&gt;schedule&lt;/code&gt;，此后在其中周而复始，&#xA;直到线程退出。骨架剥到最简，就是一个两步循环：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 每个 M 的调度循环（速写）：运行在系统栈 g0 上，永不返回&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;schedule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 找一个可运行的 G（见 9.2 的完整顺序）；找不到就阻塞在 findRunnable 内，直到有活儿&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;inheritTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;findRunnable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 切到 gp 的栈开始执行。控制权要再回到这里，靠的是下面的 mcall，而非函数返回&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;inheritTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;findRunnable&lt;/code&gt; 永不返回 &lt;code&gt;nil&lt;/code&gt;：取不到活儿，它会让 M 转入自旋或休眠，阻塞在内部直到被唤醒，&#xA;因此 &lt;code&gt;schedule&lt;/code&gt; 不必处理「无事可做」的分支。而 &lt;code&gt;execute&lt;/code&gt; 也永不返回，它跳进用户 G 的栈，&#xA;此后 &lt;code&gt;schedule&lt;/code&gt; 这一帧的栈空间就被复用了。控制权要回到调度逻辑，靠的是 9.4.2 的栈切换。&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.5 线程管理</title>
      <link>/zh-cn/part3concurrency/ch09sched/thread/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/thread/</guid>
      <description>&lt;h1 id=&#34;95-线程管理&#34;&gt;9.5 线程管理&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././model&#34;&gt;9.1&lt;/a&gt; 立下 GMP 的三层结构：G 是用户态的执行单元，P 是调度的许可证与本地资源，&#xA;M 才是真正向操作系统借来的那条腿。前几节谈 G 与 P 居多，这一节把目光落到 M 上，回答几个被&#xA;一直搁置的问题：M 到底是什么、它从哪里来、为什么 GOMAXPROCS 限的是 P 而线程数常常多于它、&#xA;一次阻塞的系统调用为何不会把别的 G 一同拖死，以及用户想把一个 Goroutine 钉死在某条线程上时&#xA;（&lt;code&gt;LockOSThread&lt;/code&gt;），运行时要为此付出什么代价。&lt;/p&gt;&#xA;&lt;p&gt;贯穿全节的一个判断是：&lt;strong&gt;线程是昂贵的资源&lt;/strong&gt;。创建它要陷入内核、要分配栈、要登记信号掩码；&#xA;销毁它同样不便宜。Go 调度器的许多设计，从复用空闲 M、到把系统调用中的 P 交接出去、再到给&#xA;线程数量设一道一万的保险丝，都是围绕「尽量少创建、尽量多复用」这一条主线展开的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;951-m-即操作系统线程&#34;&gt;9.5.1 M 即操作系统线程&lt;/h2&gt;&#xA;&lt;p&gt;M（machine）是对一条操作系统线程的抽象。进程启动时，引导线程被包装成 &lt;code&gt;m0&lt;/code&gt;，它是全局变量，&#xA;随进程一同存在，不经堆分配；此后每一个 M 都对应一条由运行时显式创建的内核线程。M 与 G 的关系&#xA;是「线程跑 Goroutine」：M 持有一个 P 后，从 P 的本地队列里取 G 来执行。裁剪后的速写只看与&#xA;线程管理相关的字段：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// m：一条操作系统线程的运行时抽象（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;g0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 调度用的系统栈 goroutine：跑调度器代码、处理信号&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;curg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 当前正在此 M 上执行的用户 goroutine&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;puintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 当前持有的 P；进入系统调用时可能被剥离&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nextp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;puintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 被唤醒后将要绑定的 P（stopm 醒来时用）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;oldp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;puintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 进入系统调用前持有的 P，留待 exitsyscall 快速取回&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;park&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;note&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 线程在此「信号量」上睡眠/唤醒，复用 M 的核心机制&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;schedlink&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;muintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 串入空闲 M 链表 / newmHandoff 链表&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lockedg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;guintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 与某个 G 互锁（LockOSThread），见 9.5.6&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lockedExt&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 外部（用户）锁定计数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lockedInt&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 内部（运行时）锁定计数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;incgo&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 是否正执行 cgo 调用&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isextra&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 是否为 cgo 回调而生的 extra-M，见 9.5.5&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;新线程经由 &lt;code&gt;newm&lt;/code&gt; → &lt;code&gt;newm1&lt;/code&gt; → &lt;code&gt;newosproc&lt;/code&gt; 创建。在 Linux 上，&lt;code&gt;newosproc&lt;/code&gt; 最终落到一次&#xA;&lt;code&gt;clone(2)&lt;/code&gt; 系统调用，所用的标志位说明了「线程」与「进程」的分野：&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.6 信号处理机制</title>
      <link>/zh-cn/part3concurrency/ch09sched/signal/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/signal/</guid>
      <description>&lt;h1 id=&#34;96-信号处理机制&#34;&gt;9.6 信号处理机制&lt;/h1&gt;&#xA;&lt;p&gt;操作系统信号是异步的、底层的：它可能在任意时刻打断任意线程，而处理器（signal handler）里&#xA;能做的事又极其受限。Go 用户想要的，却往往是用 &lt;code&gt;signal.Notify&lt;/code&gt; 把一个 channel 接上 &lt;code&gt;SIGINT&lt;/code&gt;，&#xA;在收到时优雅收尾。运行时要做的，正是在这两者之间搭一座桥：把凶险的异步信号，转化成可被&#xA;goroutine 安然消费的事件。这座桥的每一处设计，都受「信号上下文里能做什么」这一硬约束支配。&#xA;读懂了这条约束，本节其余的机制就都只是它的推论。&lt;/p&gt;&#xA;&lt;h2 id=&#34;961-异步信号安全处理器里几乎什么都不能做&#34;&gt;9.6.1 异步信号安全：处理器里几乎什么都不能做&lt;/h2&gt;&#xA;&lt;p&gt;信号处理器运行在一个被打断的、不确定的上下文里。被打断的线程此刻可能正持有 &lt;code&gt;malloc&lt;/code&gt; 的锁、&#xA;正处在某个数据结构的中间状态，甚至正在分配器内部改写元数据。一旦处理器里再去调用&#xA;&lt;code&gt;malloc&lt;/code&gt; 或获取同一把锁，便会自我死锁，或读到半成品状态而崩溃。POSIX 因此规定，信号处理器&#xA;里只能调用&lt;strong&gt;异步信号安全&lt;/strong&gt;（async-signal-safe）的函数，这是一份很短的白名单（见&#xA;&lt;code&gt;signal-safety(7)&lt;/code&gt;）：&lt;code&gt;write&lt;/code&gt;、&lt;code&gt;_exit&lt;/code&gt;、&lt;code&gt;sigaction&lt;/code&gt; 这类不碰锁、不碰分配器的系统调用在列，&#xA;而 &lt;code&gt;malloc&lt;/code&gt;、&lt;code&gt;printf&lt;/code&gt;、绝大多数 &lt;code&gt;pthread&lt;/code&gt; 加锁、乃至大部分 C 标准库都&lt;strong&gt;不在&lt;/strong&gt;其列。&lt;/p&gt;&#xA;&lt;p&gt;这条约束逼出了所有支持信号的运行时共同遵守的一条铁律：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;在处理器里只做不得不做的最少事，把真正的处理推迟到一个安全的地方去做。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;这正是经典手法&lt;strong&gt;自管道技巧&lt;/strong&gt;（self-pipe trick，W. R. Stevens 在 APUE 中给出的范式）的来由：&#xA;处理器什么都不干，只往一个事先建好的管道写一个字节；主事件循环（&lt;code&gt;select&lt;/code&gt;/&lt;code&gt;poll&lt;/code&gt;）在管道另一头&#xA;监听，于是「收到信号」被降格成一次普通的「管道可读」事件，后续处理便回到了不受限的常规上下文里。&#xA;Linux 后来用 &lt;code&gt;signalfd(2)&lt;/code&gt; 把这一手法收编进内核，让信号能直接以文件描述符的形式被 &lt;code&gt;epoll&lt;/code&gt; 消费。&lt;/p&gt;&#xA;&lt;p&gt;Go 走的是同一思路的变体，只是把那根「管道」换成了运行时内部的一个&lt;strong&gt;无锁队列&lt;/strong&gt;（&lt;a href=&#34;/zh-cn/part3concurrency/ch09sched/signal/#964-sigsend%e6%97%a0%e9%94%81%e9%98%9f%e5%88%97%e4%b8%8e%e4%b8%93%e8%81%8c%e6%8e%a5%e6%94%b6-goroutine&#34;&gt;9.6.4&lt;/a&gt;）。&#xA;理由也很 Go：自管道每次投递都要一次 &lt;code&gt;write&lt;/code&gt; 系统调用，而 Go 的信号处理与调度器、垃圾回收&#xA;深度耦合，用一个进程内的原子状态机来「写一个 bit、唤醒一个等待者」，比反复进出内核更省，也更&#xA;可控。本节接下来的几小节，便是沿着这条铁律，看 Go 如何把它落实到处理器、备用栈、队列与&#xA;专职 goroutine 这几样零件上。&lt;/p&gt;&#xA;&lt;h2 id=&#34;962-gsignal处理器自带的一段安全栈&#34;&gt;9.6.2 gsignal：处理器自带的一段安全栈&lt;/h2&gt;&#xA;&lt;p&gt;铁律的第一处落实，是给处理器一段&lt;strong&gt;专门的栈&lt;/strong&gt;。信号可能在用户 goroutine 的栈快用尽时降临，&#xA;若处理器还在这段紧绷的栈上运行，极易触发栈溢出；更麻烦的是，Go 的栈是可增长的（&lt;a href=&#34;/zh-cn/part4memory/ch14stack/&#34;&gt;14.6&lt;/a&gt;），&#xA;而栈增长本身要分配、要加锁，正是处理器里碰不得的操作。解法是 POSIX 的 &lt;code&gt;sigaltstack(2)&lt;/code&gt;：&#xA;为线程预先登记一段&lt;strong&gt;备用信号栈&lt;/strong&gt;，内核在分发信号时自动切到这段栈上执行处理器。&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.7 协作与抢占</title>
      <link>/zh-cn/part3concurrency/ch09sched/preemption/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/preemption/</guid>
      <description>&lt;h1 id=&#34;97-协作与抢占&#34;&gt;9.7 协作与抢占&lt;/h1&gt;&#xA;&lt;p&gt;我们在 &lt;a href=&#34;.././schedule&#34;&gt;9.5 调度循环&lt;/a&gt; 里遗留了一个问题：如果某个 G 执行时间过长，&#xA;其他 G 如何还能被调度？答案绕不开调度理论里的一对老概念，协作式与抢占式。&#xA;协作式调度依靠被调度方主动弃权，抢占式调度依靠调度器把被调度方被动中断。&lt;/p&gt;&#xA;&lt;p&gt;Go 运行时没有操作系统内核那样的硬件中断能力，基于工作窃取的调度器（&lt;a href=&#34;.././steal&#34;&gt;9.2&lt;/a&gt;）&#xA;本质上是先来先服务的协作式调度。它如何在不牺牲这一前提的情况下，仍能强行打断一个不肯让权的&#xA;G，是本节要讲清的设计。这条线索从一个理论问题出发：运行时凭什么不能在任意一条指令处停下一个&#xA;Goroutine。&lt;/p&gt;&#xA;&lt;h2 id=&#34;971-安全点为何不能想停就停&#34;&gt;9.7.1 安全点：为何不能想停就停&lt;/h2&gt;&#xA;&lt;p&gt;抢占的难处不在「中断」，而在「中断之后还要能正确地恢复，并让垃圾回收看懂这个 G 的栈」。&#xA;Go 是精确（precise）垃圾回收的语言，GC 必须知道一个被停住的 G，它的每一个栈槽与寄存器里&#xA;装的究竟是指针还是普通整数（&lt;a href=&#34;../../../part4memory/ch13gc/mark&#34;&gt;13.4&lt;/a&gt;）。这份「此刻哪些位置是&#xA;指针」的信息，称为&lt;strong&gt;栈映射&lt;/strong&gt;（stack map）与寄存器映射，由编译器在确定的位置才生成。换言之，&#xA;程序并非在每一条机器指令处都备有完整的指针信息，能让 GC 安全扫描的位置是离散的。&lt;/p&gt;&#xA;&lt;p&gt;这样的位置就是&lt;strong&gt;安全点&lt;/strong&gt;（safe-point）：线程执行到此，运行时能够完整地辨认其全部对象引用。&#xA;不在安全点处贸然停下一个 G，比如恰好停在写屏障序列的中段，或停在一条把指针临时拆成整数运算的&#xA;指令之间，扫描就会漏掉或误判指针，破坏 GC 的正确性。安全点把「可以安全停车的地方」限定为一组&#xA;离散点，这是抢占一切机制的地基。&lt;/p&gt;&#xA;&lt;p&gt;实现安全点有两条路。一条是&lt;strong&gt;轮询式&lt;/strong&gt;（polling），编译器在安全点插入一小段检查代码，G 自己&#xA;反复询问「有没有人要我停」，看到请求便配合停下。它简单、可移植，代价是检查指令的常驻开销，&#xA;以及一个根本盲区：若一段代码长时间不经过任何安全点（比如一个不含函数调用的紧凑循环），轮询&#xA;永远不会被执行到，请求就石沉大海。另一条是&lt;strong&gt;抢占式&lt;/strong&gt;（preemptive），由外部线程强行中断目标，&#xA;再设法把它「挪」到一个安全点上，代价是中断时机不可控，正卡在不安全的指令上时必须有办法识别&#xA;并放弃。&lt;/p&gt;&#xA;&lt;p&gt;为何一个迟迟不到安全点的线程会拖累全局？因为许多运行时操作要求&lt;strong&gt;全局停顿&lt;/strong&gt;（stop-the-world，&#xA;STW），典型如 GC 的某些阶段。STW 要等到&lt;strong&gt;所有&lt;/strong&gt; G 都停在安全点才能开始，于是总耗时由最慢的那个&#xA;线程决定。这个量有个名字，&lt;strong&gt;到达安全点的时间&lt;/strong&gt;（time-to-safepoint，TTSP）。一个 TTSP 失控的&#xA;线程，比如陷在死循环里，会把整次 STW 的延迟拖到不可接受。降低 TTSP 的尾部，正是抢占机制要解决的&#xA;工程目标。&lt;/p&gt;&#xA;&lt;h2 id=&#34;972-协作式抢占搭栈增长检查的便车&#34;&gt;9.7.2 协作式抢占：搭栈增长检查的便车&lt;/h2&gt;&#xA;&lt;p&gt;Go 最早的抢占是纯协作式的，且复用了一处现成的安全点：函数序言里的栈增长检查&#xA;（&lt;a href=&#34;../../../part4memory/ch14stack/readme&#34;&gt;14 执行栈管理&lt;/a&gt;）。每个非 &lt;code&gt;nosplit&lt;/code&gt; 函数在入口都会比较栈指针 SP 与&#xA;&lt;code&gt;g.stackguard0&lt;/code&gt;，SP 越界就触发 &lt;code&gt;morestack&lt;/code&gt;，转入 &lt;code&gt;newstack&lt;/code&gt; 去扩张栈。这处检查恰好是一个同步&#xA;安全点：此刻 G 的栈是完整可扫描的。&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.8 系统监控</title>
      <link>/zh-cn/part3concurrency/ch09sched/sysmon/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/sysmon/</guid>
      <description>&lt;h1 id=&#34;98-系统监控&#34;&gt;9.8 系统监控&lt;/h1&gt;&#xA;&lt;p&gt;调度器的常规路径已在 &lt;a href=&#34;.././schedule&#34;&gt;9.4 调度循环&lt;/a&gt; 讲清：一个 M 绑定一个 P，&#xA;从队列里取 Goroutine，运行，再取下一个。这条路径有一个前提，它本身得有机会跑起来。&#xA;可一旦所有 P 都陷进长时间的系统调用，或某个 Goroutine 死循环占着 P 不放，常规调度便&#xA;卡住了，没有谁来抢回 P，也没有谁来 poll 网络。换句话说，协作式的、跑在 P 上的逻辑，&#xA;管不了「P 本身都动不了」这种局面。&lt;/p&gt;&#xA;&lt;p&gt;Go 的答案是在调度循环之外另设一条命脉。运行时启动时，&lt;code&gt;main&lt;/code&gt; 用 &lt;code&gt;newm&lt;/code&gt; 拉起一个特殊的 M，&#xA;专门运行 &lt;code&gt;sysmon&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;haveSysmon&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;systemstack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;newm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sysmon&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 一个不绑定 P 的特殊 M&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这个 M 的特殊之处在于它&lt;strong&gt;不持有 P，也永不进入常规调度&lt;/strong&gt;。它靠运行时自带的通知机制&#xA;（Linux 上是 futex）睡眠与唤醒，不依赖调度器，因此即便所有 P 都被卡住，它照样醒来巡视一圈。&#xA;这正是 watchdog（看门狗）的设计：把监督者放在被监督系统之外，它才看得见系统的停摆。&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.9 网络轮询器</title>
      <link>/zh-cn/part3concurrency/ch09sched/poller/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/poller/</guid>
      <description>&lt;h1 id=&#34;99-网络轮询器&#34;&gt;9.9 网络轮询器&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;源码事实核对自 &lt;code&gt;src/runtime/netpoll.go&lt;/code&gt; 及各平台实现&#xA;（&lt;code&gt;netpoll_epoll.go&lt;/code&gt;、&lt;code&gt;netpoll_kqueue.go&lt;/code&gt; 等）与 &lt;code&gt;src/internal/poll/fd_unix.go&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Go 的网络代码看起来是阻塞式的：&lt;code&gt;conn.Read&lt;/code&gt; 会「卡」在那里等数据。可如果它真卡住了所在的&#xA;操作系统线程，那么一万个等待网络的 goroutine 就要占用一万个线程，&lt;a href=&#34;.././model&#34;&gt;9.1&lt;/a&gt; 苦心经营的&#xA;M:N 模型瞬间崩塌。让阻塞式写法仍能规模化的，是网络轮询器（netpoller）。它背后是一段关于&#xA;「如何用少数线程照看海量连接」的漫长历史，本节先把这段历史与它背后的设计轴讲清楚，&#xA;再看 Go 如何把成熟的事件机制藏进运行时，让你写同步代码、跑事件驱动的 I/O。&lt;/p&gt;&#xA;&lt;h2 id=&#34;991-c10k-与就绪通知的演化&#34;&gt;9.9.1 C10k 与就绪通知的演化&lt;/h2&gt;&#xA;&lt;p&gt;2000 年前后，Dan Kegel 提出了著名的 &lt;strong&gt;C10k 问题&lt;/strong&gt;：一台服务器能否同时处理一万个并发连接？&#xA;他的论点是当时的硬件其实够用，瓶颈在软件的 I/O 策略。最朴素的「一连接一线程」模型撑不住：&#xA;每个线程预留以兆字节计的栈，一万个线程就是数 GB；加上内核在成千上万个线程间切换的开销，&#xA;调度器很快不堪重负。出路是&lt;strong&gt;基于就绪的多路复用&lt;/strong&gt;：让少数线程照看大量 fd，只在某个 fd 就绪时&#xA;才去动它。&lt;/p&gt;&#xA;&lt;p&gt;就绪通知机制本身也经历了演化。把代价写成大 O 最能看清这条主线。设并发连接数为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，&#xA;某一刻就绪的连接数为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;k&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6944em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;（通常 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo&gt;≪&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;k \ll n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7335em;vertical-align:-0.0391em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;≪&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;）：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;select&lt;/code&gt; / &lt;code&gt;poll&lt;/code&gt;：&lt;strong&gt;每次调用 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;。调用方每次都要把整个 fd 集合交给内核，内核线性扫&#xA;一遍标出就绪者，返回后调用方再扫一遍找出它们。集合本身在用户态与内核态间反复拷贝，&#xA;连接一多，这种全量扫描与拷贝就成了瓶颈。&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;epoll&lt;/code&gt;（Linux，Libenzi，开发版内核 2.5.44 / 2002，稳定版 2.6.0 / 2003）：兴趣集合通过&#xA;&lt;code&gt;epoll_ctl&lt;/code&gt; 在内核里&lt;strong&gt;登记一次&lt;/strong&gt;并常驻，&lt;code&gt;epoll_wait&lt;/code&gt; 只返回&lt;strong&gt;就绪的&lt;/strong&gt;那些 fd，单次调用的&#xA;代价是 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(k)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;一个常见的不精确说法是「epoll 是 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;」。更准确的表述是：登记的成本被 &lt;code&gt;epoll_ctl&lt;/code&gt;&#xA;摊销，&lt;code&gt;epoll_wait&lt;/code&gt; 的代价正比于&lt;strong&gt;就绪事件数 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;k&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6944em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;，而非 fd 总数 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。正是这一点把&#xA;&lt;code&gt;select&lt;/code&gt;/&lt;code&gt;poll&lt;/code&gt; 的「每调用 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;」改进成了「每调用 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(k)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;」，在 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo&gt;≪&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;k \ll n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7335em;vertical-align:-0.0391em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;≪&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的长连接场景里&#xA;是数量级的差距。&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.10 计时器</title>
      <link>/zh-cn/part3concurrency/ch09sched/timer/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/timer/</guid>
      <description>&lt;h1 id=&#34;910-计时器&#34;&gt;9.10 计时器&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;time.Sleep&lt;/code&gt;、&lt;code&gt;time.After&lt;/code&gt;、&lt;code&gt;time.Timer&lt;/code&gt;、&lt;code&gt;time.Ticker&lt;/code&gt;，乃至网络读写的 &lt;code&gt;SetDeadline&lt;/code&gt;，背后都是&#xA;同一套计时器机制。它要回答一个看似简单、实则微妙的数据结构问题：成千上万个计时器同时存在时，&#xA;如何高效地知道「下一个该在什么时候叫醒谁」，又不为此空耗一个线程。这一节从这个抽象问题讲起，&#xA;看清各种解法的取舍，再落到 Go 的选择与它的演进。&lt;/p&gt;&#xA;&lt;h2 id=&#34;9101-计时器的数据结构问题&#34;&gt;9.10.1 计时器的数据结构问题&lt;/h2&gt;&#xA;&lt;p&gt;一套计时器要支持三种操作：START（登记一个超时）、STOP（到期前取消）、到期检查 / EXPIRY&#xA;（时钟前进，触发所有到点的）。难点在于时钟可能每秒被检查成千上万次，无论是否真有计时器到期，&#xA;所以「每次 tick」的代价必须低，同时 START / STOP 也要快。几种朴素方案的复杂度对比，正是&#xA;Varghese 与 Lauck 1987 年那篇经典论文的出发点：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;方案&lt;/th&gt;&#xA;          &lt;th&gt;START&lt;/th&gt;&#xA;          &lt;th&gt;STOP&lt;/th&gt;&#xA;          &lt;th&gt;每 tick 检查&lt;/th&gt;&#xA;          &lt;th&gt;取出到期&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;无序链表&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 扫描&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;有序链表&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 看表头&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;最小堆&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;log&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(\log n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mop&#34;&gt;lo&lt;span style=&#34;margin-right:0.01389em;&#34;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;log&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(\log n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mop&#34;&gt;lo&lt;span style=&#34;margin-right:0.01389em;&#34;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 看堆顶&lt;/td&gt;&#xA;          &lt;td&gt;每个 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;log&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(\log n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mop&#34;&gt;lo&lt;span style=&#34;margin-right:0.01389em;&#34;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;这里有一个常被混淆的精确之处：堆的「取最小」是 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，但每触发一个到期计时器要付 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;log&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(\log n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mop&#34;&gt;lo&lt;span style=&#34;margin-right:0.01389em;&#34;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&#xA;的删除与下沉。所以「每 tick 便宜」只是说检查便宜，真正排空 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;k&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6944em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个到期者是 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mi&gt;log&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(k \log n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mop&#34;&gt;lo&lt;span style=&#34;margin-right:0.01389em;&#34;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。三种&#xA;朴素方案各有所长，却没有一种在 START、STOP 与到期检查上同时取胜，时间轮正是为打破这个僵局&#xA;而生。&lt;/p&gt;</description>
    </item>
    <item>
      <title>9.11 NUMA 感知与调度器的未来</title>
      <link>/zh-cn/part3concurrency/ch09sched/numa/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch09sched/numa/</guid>
      <description>&lt;h1 id=&#34;911-numa-感知与调度器的未来&#34;&gt;9.11 NUMA 感知与调度器的未来&lt;/h1&gt;&#xA;&lt;p&gt;前面各节描述的调度器有一个从未明说的假设：任何 M 访问内存的速度都一样快，任何两个 P 之间&#xA;搬动一个 G 的代价都相同。在笔记本和单路服务器上，这个假设近乎成立。可一旦把程序放到大型&#xA;多路服务器上，它就开始失真，而且核数越多、失真越大。本节谈这道裂缝：它从何而来，Go 调度器&#xA;为何至今对它视而不见，一份曾被认真设计却没有落地的 NUMA 感知方案，以及今天的用户该如何&#xA;绕过去。&lt;/p&gt;&#xA;&lt;h2 id=&#34;9111-numa内存不再是一块平地&#34;&gt;9.11.1 NUMA：内存不再是一块平地&lt;/h2&gt;&#xA;&lt;p&gt;早年的对称多处理（SMP）机器里，所有 CPU 经一条共享总线访问同一块内存，访存延迟与是哪个核&#xA;发起的无关。这种「一块平地」的内存模型简单，却随核数增长而失效：共享总线成了瓶颈，每多一个&#xA;核就多一分争用。&lt;strong&gt;NUMA（non-uniform memory access，非均匀访存）&lt;/strong&gt; 是工业界给出的答案。&#xA;把机器拆成若干 &lt;strong&gt;节点&lt;/strong&gt;（node），每个节点是一个 socket 加上直连在它身上的一片本地内存，&#xA;节点之间用片间互连（Intel 的 UPI、AMD 的 Infinity Fabric）连成一张网。CPU 访问本节点的&#xA;内存走最短路径，访问别节点的内存则要跨越互连，多一跳甚至多几跳。&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    subgraph N0[&amp;#34;NUMA 节点 0&amp;#34;]&#xA;      direction TB&#xA;      C0[&amp;#34;CPU 0（多核 &amp;#43; LLC）&amp;#34;]&#xA;      M0[(&amp;#34;本地内存 0&amp;#34;)]&#xA;      C0 ---|&amp;#34;本地：快&amp;#34;| M0&#xA;    end&#xA;    subgraph N1[&amp;#34;NUMA 节点 1&amp;#34;]&#xA;      direction TB&#xA;      C1[&amp;#34;CPU 1（多核 &amp;#43; LLC）&amp;#34;]&#xA;      M1[(&amp;#34;本地内存 1&amp;#34;)]&#xA;      C1 ---|&amp;#34;本地：快&amp;#34;| M1&#xA;    end&#xA;    C0 -. &amp;#34;远程：跨互连，更慢&amp;#34; .-&amp;gt; M1&#xA;    C1 -. &amp;#34;远程：跨互连，更慢&amp;#34; .-&amp;gt; M0&lt;/pre&gt;&#xA;&lt;p&gt;代价是实打实的。本地与远程的访存延迟通常相差一到两倍，跨节点带宽也低于本地带宽，具体数字&#xA;随平台而变，可在 Linux 上用 &lt;code&gt;numactl --hardware&lt;/code&gt; 读出节点拓扑与节点间的相对距离矩阵。&#xA;比延迟更隐蔽的是 &lt;strong&gt;缓存一致性的开销&lt;/strong&gt;：当一个核要写一行被别的节点缓存着的数据时，一致性协议&#xA;（MESI 一族）要先让远端那份副本失效，这条「失效消息」也得跨互连往返。于是 NUMA 上的伪共享&#xA;（false sharing，&lt;a href=&#34;../../../part4memory/ch12alloc/component&#34;&gt;12.2&lt;/a&gt;）比 SMP 上更疼：同一缓存行&#xA;被两个节点的核反复争夺，每次写都要付一次跨节点的一致性往返。&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.1 通道与 CSP 的工程化</title>
      <link>/zh-cn/part3concurrency/ch10chan/model/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch10chan/model/</guid>
      <description>&lt;h1 id=&#34;101-通道与-csp-的工程化&#34;&gt;10.1 通道与 CSP 的工程化&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;本节内容提供一个线上演讲：&lt;a href=&#34;https://www.youtube.com/watch?v=d7fFCGGn0Wc&#34;&gt;YouTube 在线&lt;/a&gt;，&#xA;&lt;a href=&#34;https://changkun.de/s/chansrc/&#34;&gt;Google Slides 讲稿&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;CSP 给了 Go 一个主张：进程之间不共享状态，只靠传递消息来协调（&lt;a href=&#34;../../../part1overview/ch01intro/csp&#34;&gt;1.3&lt;/a&gt;）。&#xA;这一节关心的是另一个问题：一门工程语言要把这个主张落地，需要把「通信」做成什么样子，&#xA;才能让普通程序员顺手用对。Go 的答案是 channel，一个一等的同步兼通信原语。本节先把它在&#xA;语言表层的模型讲清楚，它的类型、收发语法、有无缓冲的两种语义、方向限定与 nil 行为，&#xA;为后续各节深入运行时实现（&lt;a href=&#34;.././impl&#34;&gt;10.2&lt;/a&gt;–&lt;a href=&#34;.././pattern&#34;&gt;10.7&lt;/a&gt;）铺好直觉。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1011-把通信做成一等原语&#34;&gt;10.1.1 把通信做成一等原语&lt;/h2&gt;&#xA;&lt;p&gt;「不要以共享内存的方式通信，而要以通信的方式共享内存。」这句格言常被引用，但它在 Go 里不是&#xA;一句口号，而是由 channel 这个具体的语言构造兑现的。CSP 的渊源、它与 1978 年原始论文的异同，&#xA;已在 &lt;a href=&#34;../../../part1overview/ch01intro/csp&#34;&gt;1.3&lt;/a&gt; 交代，这里不再重复，只接着说 Go 在工程上做了&#xA;什么取舍。&lt;/p&gt;&#xA;&lt;p&gt;把 CSP 的「通信」搬进一门通用语言，至少要回答三件事：通信经由什么载体、这个载体能否像普通值&#xA;一样使用、它如何与类型系统咬合。Go 给出的载体是 channel，并且把它做成了&lt;strong&gt;一等&lt;/strong&gt;（first-class）&#xA;的：channel 是有类型的值，可以由 &lt;code&gt;make&lt;/code&gt; 创建、用变量持有、作为参数传给函数、作为返回值传出、&#xA;存进结构体字段、放进 slice 或 map。这一点与 Hoare 原始 CSP 按进程名直接通信的设计不同&#xA;（&lt;a href=&#34;../../../part1overview/ch01intro/csp&#34;&gt;1.3.2&lt;/a&gt;），也正是 Go 把抽象主张变为可组合工具的关键:&#xA;既然 channel 是值，「把一个通信端口交给另一段代码」就退化成了普通的传参，无需任何特殊机制。&lt;/p&gt;&#xA;&lt;p&gt;channel 同时承担两件常被分开的事:&lt;strong&gt;同步&lt;/strong&gt;与&lt;strong&gt;数据传递&lt;/strong&gt;。一次收发既搬运了一个值，又在收发&#xA;双方之间建立了一个发生序（happens-before）关系，后者保证了「发送方在发送前写下的内存，接收方&#xA;在接收后一定看得见」。换句话说，channel 不仅是一根传值的管子，它还是同步原语。这条发生序&#xA;保证是 channel 能替代显式加锁的根基，其精确条文留待 &lt;a href=&#34;.././lockfree&#34;&gt;10.6&lt;/a&gt; 与&#xA;&lt;a href=&#34;../../ch11sync/mem&#34;&gt;11.9&lt;/a&gt; 展开，本节只需记住:&lt;strong&gt;收发自带同步&lt;/strong&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.2 hchan：通道的内部结构</title>
      <link>/zh-cn/part3concurrency/ch10chan/impl/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch10chan/impl/</guid>
      <description>&lt;h1 id=&#34;102-hchan通道的内部结构&#34;&gt;10.2 hchan：通道的内部结构&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././readme&#34;&gt;10.1&lt;/a&gt; 从 CSP 的角度交代了通道在语言里的位置：它是显式的消息信道，&#xA;把「通信」与「同步」合二为一。这一节把它拆开来看。一个 channel 在运行时里就是一个名为 &lt;code&gt;hchan&lt;/code&gt;&#xA;的结构体，它的全部秘密不过是一把锁、一圈环形缓存、外加两条等待队列。结构虽小，每个字段的存在&#xA;都对应一处设计上的考量，读懂这几样东西，后面收发与 select 的全部逻辑（&lt;a href=&#34;.././sendrecv&#34;&gt;10.3&lt;/a&gt;–&#xA;&lt;a href=&#34;.././lockfree&#34;&gt;10.6 内存模型与无锁演进&lt;/a&gt;）就只是「在这张图上搬运数据、挂起与唤醒 goroutine」。&lt;/p&gt;&#xA;&lt;p&gt;沿用本书讲运行时数据结构的惯例，下文给出的结构体是&lt;strong&gt;裁剪后的速写&lt;/strong&gt;，只留与设计相关的字段，&#xA;并在注释里说明它为何存在。完整定义可对照 &lt;code&gt;runtime/chan.go&lt;/code&gt; 与 &lt;code&gt;runtime/runtime2.go&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1021-三条不变式先看为什么&#34;&gt;10.2.1 三条不变式：先看「为什么」&lt;/h2&gt;&#xA;&lt;p&gt;在逐字段拆解之前，先抄下 &lt;code&gt;chan.go&lt;/code&gt; 开头那段注释立下的三条不变式，它们是整套结构的设计意图，&#xA;比任何字段说明都更能说清「为何如此」：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;c.sendq&lt;/code&gt; 与 &lt;code&gt;c.recvq&lt;/code&gt; 至少有一个为空（唯一的例外是：一个无缓冲 channel 上，某个&#xA;goroutine 用 select 同时在收和发两侧挂着）；&lt;/li&gt;&#xA;&lt;li&gt;对缓冲 channel，&lt;code&gt;c.qcount &amp;gt; 0&lt;/code&gt; 蕴含 &lt;code&gt;c.recvq&lt;/code&gt; 为空；&lt;/li&gt;&#xA;&lt;li&gt;对缓冲 channel，&lt;code&gt;c.qcount &amp;lt; c.dataqsiz&lt;/code&gt; 蕴含 &lt;code&gt;c.sendq&lt;/code&gt; 为空。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这三条用日常语言说就是一句话：&lt;strong&gt;缓存里还有货，就不该有接收方在干等；缓存还有空位，就不该有&#xA;发送方在干等。&lt;/strong&gt; 一旦缓存与「干等的人」同时存在，说明本可以直接成交却没成交，那是 bug。整个&#xA;收发实现（&lt;a href=&#34;.././sendrecv&#34;&gt;10.3&lt;/a&gt;、&lt;a href=&#34;.././sendrecv&#34;&gt;10.3&lt;/a&gt;）所做的，正是时刻维持这三条：能直接成交就&#xA;直接成交，实在成交不了才把自己挂进等待队列。带着这个意图回看下面的字段，就不再是枯燥的清单。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1022-hchan-的速写&#34;&gt;10.2.2 hchan 的速写&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// hchan：一个 channel 的运行时表示（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hchan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;qcount&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 缓存中现有的元素个数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dataqsiz&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 环形缓存的容量（make 的第二个参数）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 指向一段可存 dataqsiz 个元素的连续数组&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;elemsize&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint16&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 单个元素的大小（由元素类型推出，缓存于此免去反复查表）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;closed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 是否已 close&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;elemtype&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 元素类型：拷贝元素、写屏障、给 buf 标注 GC 信息都要用它&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sendx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 环形缓存的写游标（下一个待写入的槽）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;recvx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 环形缓存的读游标（下一个待读出的槽）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;recvq&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;waitq&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 阻塞的接收方队列（ &amp;lt;-ch ）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sendq&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;waitq&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 阻塞的发送方队列（ ch&amp;lt;- ）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mutex&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;              &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 一把锁，守护以上所有字段&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;字段大体分作三组。&lt;code&gt;qcount&lt;/code&gt;、&lt;code&gt;dataqsiz&lt;/code&gt;、&lt;code&gt;buf&lt;/code&gt;、&lt;code&gt;sendx&lt;/code&gt;、&lt;code&gt;recvx&lt;/code&gt; 五个合起来描述那圈&lt;strong&gt;环形缓存&lt;/strong&gt;&#xA;（10.2.3）；&lt;code&gt;recvq&lt;/code&gt;、&lt;code&gt;sendq&lt;/code&gt; 是两条&lt;strong&gt;等待队列&lt;/strong&gt;（10.2.4）；&lt;code&gt;closed&lt;/code&gt; 记录关闭状态，&#xA;&lt;code&gt;elemsize&lt;/code&gt; 与 &lt;code&gt;elemtype&lt;/code&gt; 是为搬运元素备下的元信息。其中 &lt;code&gt;dataqsiz&lt;/code&gt; 在创建之后再不写入，&#xA;因此运行时可以无锁地读它来快速判断 channel 是否有缓存（&lt;code&gt;full&lt;/code&gt; 等辅助函数依赖这一点）。&#xA;至于 &lt;code&gt;elemtype&lt;/code&gt;，它不只用于按值拷贝元素和触发写屏障，还在创建时被交给分配器，&#xA;用来给 &lt;code&gt;buf&lt;/code&gt; 标注「这段内存里哪些位置是指针」，这一点到 10.2.5 会再回来。&#xA;（go1.26 的 &lt;code&gt;hchan&lt;/code&gt; 还有 &lt;code&gt;timer&lt;/code&gt; 与 &lt;code&gt;bubble&lt;/code&gt; 两个字段，分别服务于 &lt;code&gt;time&lt;/code&gt; 定时器与&#xA;&lt;code&gt;testing/synctest&lt;/code&gt;，与本节的主线无关，速写中略去。）&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.3 收发与直接传递</title>
      <link>/zh-cn/part3concurrency/ch10chan/sendrecv/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch10chan/sendrecv/</guid>
      <description>&lt;h1 id=&#34;103-收发与直接传递&#34;&gt;10.3 收发与直接传递&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././impl&#34;&gt;10.2&lt;/a&gt; 把 &lt;code&gt;hchan&lt;/code&gt; 这副骨架摆了出来：一把锁、一个环形缓存 &lt;code&gt;buf&lt;/code&gt;，外加发送方队列&#xA;&lt;code&gt;sendq&lt;/code&gt; 与接收方队列 &lt;code&gt;recvq&lt;/code&gt;。这一节让骨架动起来，回答一次 &lt;code&gt;ch &amp;lt;- v&lt;/code&gt; 与 &lt;code&gt;v := &amp;lt;-ch&lt;/code&gt; 在运行时&#xA;里究竟发生了什么。读懂这条收发路径，channel 的两条最常被追问的性质,无缓冲 channel 为何是&#xA;一次「会合」（rendezvous）、为何对它而言接收发生在发送完成之前（&lt;a href=&#34;../../ch11sync/mem&#34;&gt;11.9&lt;/a&gt;），&#xA;都会落到同一处机制上：&lt;strong&gt;直接传递&lt;/strong&gt;（direct send / receive）。&lt;/p&gt;&#xA;&lt;p&gt;收发的设计要同时满足三个约束。其一，正确：不能丢数据，不能让已关闭的 channel 吞下新值。&#xA;其二，快：无竞争时一次收发应当只是「拿锁、拷一次、放锁」，热路径上连锁都尽量不碰。其三，&#xA;公平：多个发送方或接收方阻塞在同一个 channel 上时，唤醒次序应当可预期（&lt;a href=&#34;/zh-cn/part3concurrency/ch10chan/sendrecv/#1035-fifo-%e5%85%ac%e5%b9%b3%e4%b8%8e%e4%b8%80%e5%a4%84%e5%8e%86%e5%8f%b2%e6%95%99%e8%ae%ad&#34;&gt;10.3.5&lt;/a&gt;）。&#xA;下文先走通发送，再以对称的笔法带过接收，最后落到那处把二者统一起来的优化。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1031-chansend-的三岔决策&#34;&gt;10.3.1 chansend 的三岔决策&lt;/h2&gt;&#xA;&lt;p&gt;先用一张可交互图建立直觉：有缓冲 channel 是一条定长队列，发送把值放进缓冲、接收从缓冲取走；缓冲满则发送方阻塞，缓冲空则接收方阻塞。可调 &lt;code&gt;cap&lt;/code&gt;，或手动收发。&lt;/p&gt;&#xA;&lt;div class=&#34;viz&#34; data-viz=&#34;channel&#34;&gt;&lt;/div&gt;&#xA;&lt;p&gt;编译器把 &lt;code&gt;ch &amp;lt;- v&lt;/code&gt; 译成 &lt;code&gt;chansend1&lt;/code&gt;，后者转调更通用的 &lt;code&gt;chansend&lt;/code&gt;。&lt;code&gt;chansend&lt;/code&gt; 的第三个参数&#xA;&lt;code&gt;block&lt;/code&gt; 区分阻塞收发与 &lt;code&gt;select&lt;/code&gt; 里的非阻塞分支（&lt;a href=&#34;.././select&#34;&gt;10.5&lt;/a&gt;）。剥去竞态检测、&lt;code&gt;synctest&lt;/code&gt;&#xA;气泡与统计代码，它的主干是一道清晰的三岔决策：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// chansend：向 channel 发送 ep 指向的值（裁剪后的速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;chansend&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hchan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ep&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;block&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 向 nil channel 发送：永久阻塞&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;block&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;gopark&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;waitReasonChanSendNilChan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 不再返回&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;throw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;unreachable&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 非阻塞快路径：不加锁就能判定的失败（详见 10.3.4）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;block&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;closed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;full&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;closed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;             &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 向已关闭 channel 发送：panic&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;unlock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;panic&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;plainError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;send on closed channel&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 岔路一：recvq 里有等待的接收方 -&amp;gt; 直接传递，绕过 buf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;recvq&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;dequeue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;send&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;unlock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 岔路二：缓存还有空位 -&amp;gt; 把值拷进环形 buf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;qcount&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dataqsiz&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;qp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;chanbuf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sendx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;typedmemmove&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;elemtype&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;qp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 拷入 buf 的 sendx 槽&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sendx&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sendx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dataqsiz&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sendx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 环形回绕&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;qcount&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;unlock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 岔路三：无接收方、buf 也满 -&amp;gt; 把自己挂进 sendq 并 park&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ……见 10.3.3&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;三岔的优先级本身就是设计：&lt;strong&gt;有等待的接收方时，永远优先直接交给它&lt;/strong&gt;，哪怕这是个带缓存且缓存&#xA;里还有空位的 channel。直觉上似乎该「先填缓存」，但只要 &lt;code&gt;recvq&lt;/code&gt; 非空，就说明缓存此刻必然为空&#xA;（否则接收方早从缓存取走了，不会阻塞），于是绕过缓存直接交付不仅合法，还省下一次拷贝。&#xA;这处优先级是下一节那个优化的前提。&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.4 关闭的语义</title>
      <link>/zh-cn/part3concurrency/ch10chan/close/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch10chan/close/</guid>
      <description>&lt;h1 id=&#34;104-关闭的语义&#34;&gt;10.4 关闭的语义&lt;/h1&gt;&#xA;&lt;p&gt;前几节里，发送与接收都是「一对一」的会合：一次发送对上一次接收，多余的一方便阻塞等待。&#xA;关闭是 channel 上唯一的「一对多」操作。&lt;code&gt;close(ch)&lt;/code&gt; 由一个 Goroutine 发出，却能在一瞬间&#xA;唤醒所有正阻塞在这条 channel 上的接收方，并让所有阻塞的发送方就地 panic。这种「一次广播、&#xA;众人皆醒」的能力，使关闭从一个看似不起眼的清理动作，变成了 Go 并发里最常用的取消与收尾&#xA;机制。done channel 模式、&lt;code&gt;context&lt;/code&gt; 的取消（&lt;a href=&#34;../../../part3concurrency/ch11sync/context&#34;&gt;11.8&lt;/a&gt;），&#xA;根子都在这里。&lt;/p&gt;&#xA;&lt;p&gt;这一节回答三个问题：&lt;code&gt;close&lt;/code&gt; 在运行时究竟做了什么，为什么它能成为广播原语；从一条已关闭的&#xA;channel 上接收会读到什么，&lt;code&gt;for range&lt;/code&gt; 因何而终止；以及语言为关闭划下的三条 panic 红线，&#xA;为什么它们宁可让程序当场崩溃，也不肯悄悄容错。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1041-close-做了什么一次加锁广播&#34;&gt;10.4.1 close 做了什么：一次加锁广播&lt;/h2&gt;&#xA;&lt;p&gt;关闭的实现集中在 &lt;code&gt;runtime.closechan&lt;/code&gt; 一个函数里。它的骨架可以裁剪成三段:先在锁内置位&#xA;&lt;code&gt;closed&lt;/code&gt;，再把 &lt;code&gt;recvq&lt;/code&gt; 与 &lt;code&gt;sendq&lt;/code&gt; 上所有等待者一次性摘下、装进一个本地链表，最后解锁、&#xA;统一唤醒。下面是只保留设计要点的速写:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;44&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;45&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;46&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;47&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;48&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;49&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;50&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;51&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// closechan：关闭一条 channel（速写，去掉了竞态检测与 synctest 分支）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;closechan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hchan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;panic&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;plainError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;close of nil channel&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 红线一：close(nil)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;closed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;unlock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;panic&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;plainError&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;close of closed channel&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 红线二：重复关闭&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;closed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                                      &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 置位，且不可逆&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;glist&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gList&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 先收集，后唤醒：唤醒要在锁外做&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 唤醒所有接收者：每个都将拿到零值，ok == false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;recvq&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;dequeue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;elem&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;typedmemclr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;elemtype&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;elem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 把接收方的目标内存清零，即「零值」&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;elem&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;param&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 关键标志：本次接收并非来自一次真正的发送&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;glist&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;push&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 唤醒所有发送者：它们醒来后会 panic&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sendq&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;dequeue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;elem&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;param&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;success&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;glist&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;push&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;unlock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 锁已释放，再把整张表逐一交还调度器&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;glist&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;empty&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;glist&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;pop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;goready&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;有三处设计值得点出。&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.5 select 的实现</title>
      <link>/zh-cn/part3concurrency/ch10chan/select/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch10chan/select/</guid>
      <description>&lt;h1 id=&#34;105-select-的实现&#34;&gt;10.5 select 的实现&lt;/h1&gt;&#xA;&lt;p&gt;前几节把单个 channel 的收发（&lt;a href=&#34;.././sendrecv&#34;&gt;10.3&lt;/a&gt;）讲清楚了。可现实里的&#xA;Goroutine 往往不只盯着一条 channel：它要在多个收发操作中，对&lt;strong&gt;先就绪的那个&lt;/strong&gt;作出反应，&#xA;还要能在「一个都没就绪」时不被阻塞。&lt;code&gt;select&lt;/code&gt; 正是为此而生。它的语义看似简单，落到实现上却要同时&#xA;解决两个棘手问题：多个就绪分支时如何&lt;strong&gt;公平&lt;/strong&gt;地选一个，以及为执行一次 select 而锁住多条 channel&#xA;时如何&lt;strong&gt;不死锁&lt;/strong&gt;。这两点决定了 &lt;code&gt;selectgo&lt;/code&gt; 的全部结构，本节就围绕它们展开。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ch1 可接收时执行&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;use&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ch2 可发送时执行&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;sent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 上面都未就绪时执行（可选）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;nonblocking&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;约定先摆在前面。每个 case 描述一个 channel 操作（收或发），&lt;code&gt;select&lt;/code&gt; 求值后&lt;strong&gt;至多执行一个&lt;/strong&gt;&#xA;case 的通信。若有多个 case 同时就绪，从中&lt;strong&gt;等概率随机&lt;/strong&gt;挑一个；若都未就绪，有 &lt;code&gt;default&lt;/code&gt; 则执行&#xA;&lt;code&gt;default&lt;/code&gt;（这使整条 select 变成非阻塞），无 &lt;code&gt;default&lt;/code&gt; 则阻塞，直到某个 case 就绪。语言规范&#xA;（&lt;a href=&#34;https://go.dev/ref/spec#Select_statements&#34;&gt;Select statements&lt;/a&gt;）把这套语义钉死，运行时&#xA;负责兑现，编译器负责把 source 形态翻译成运行时能消化的数据。&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.6 内存模型与无锁演进</title>
      <link>/zh-cn/part3concurrency/ch10chan/lockfree/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch10chan/lockfree/</guid>
      <description>&lt;h1 id=&#34;106-内存模型与无锁演进&#34;&gt;10.6 内存模型与无锁演进&lt;/h1&gt;&#xA;&lt;p&gt;前几节把 channel 拆到了零件：&lt;a href=&#34;.././sendrecv&#34;&gt;10.3&lt;/a&gt; 的直接交付（direct handoff）让收发两端&#xA;绕过环形缓冲直接传值，&lt;a href=&#34;.././select&#34;&gt;10.5&lt;/a&gt; 的 &lt;code&gt;select&lt;/code&gt; 在多个就绪分支间做随机选择。这些机制&#xA;解释了 channel「怎么跑」。本节回答两个更上层的问题：channel 给并发程序提供了&lt;strong&gt;什么可见性&#xA;承诺&lt;/strong&gt;，以及一个常被追问的工程疑案，channel 为什么至今仍是「一把锁加一个队列」，而不是&#xA;传说中更快的无锁结构。前者把 channel 接回 &lt;a href=&#34;../../ch11sync/mem&#34;&gt;11.9&lt;/a&gt; 的内存模型，后者则是&#xA;一则关于「正确性与可维护性如何压过峰值性能」的真实取舍。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1061-channel-的内存模型承诺&#34;&gt;10.6.1 channel 的内存模型承诺&lt;/h2&gt;&#xA;&lt;p&gt;channel 不只是传数据的管子，它同时是建立 happens-before 的同步点。&lt;a href=&#34;../../ch11sync/mem&#34;&gt;11.9&lt;/a&gt;&#xA;给出了 Go 内存模型的全貌，这里把其中与 channel 相关的四条规则单独拎出，逐条读懂它们的含义。&#xA;Go 内存模型（go.dev/ref/mem，2022 年 6 月版）对 channel 的规定如下，我们沿用 1.19 修订后的&#xA;术语 &lt;em&gt;synchronized before&lt;/em&gt;（同步先于，记作 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo&gt;&amp;lt;&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;&amp;lt;&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5782em;vertical-align:-0.0391em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;）：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;一次发送&lt;strong&gt;同步先于&lt;/strong&gt;对应接收的完成。对任意 channel（无论是否带缓冲）均成立。&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;close(ch)&lt;/code&gt; &lt;strong&gt;同步先于&lt;/strong&gt;一个因 channel 关闭而返回零值的接收。&lt;/li&gt;&#xA;&lt;li&gt;对&lt;strong&gt;无缓冲&lt;/strong&gt; channel，一次接收&lt;strong&gt;同步先于&lt;/strong&gt;对应发送的完成。&lt;/li&gt;&#xA;&lt;li&gt;对容量为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;C&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.07153em;&#34;&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的缓冲 channel，第 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;k&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6944em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 次接收&lt;strong&gt;同步先于&lt;/strong&gt;第 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;k+C&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7778em;vertical-align:-0.0833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03148em;&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.07153em;&#34;&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 次发送的完成。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;第 1 条是最朴素的直觉：把数据放进 channel 之前的所有写入，接收方在拿到这份数据之后都能看见。&#xA;这正是「不要用共享内存来通信，而要用通信来共享内存」这句格言的形式化基础。一段最小的例子：&lt;/p&gt;</description>
    </item>
    <item>
      <title>10.7 工程实践与跨语言对照</title>
      <link>/zh-cn/part3concurrency/ch10chan/pattern/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch10chan/pattern/</guid>
      <description>&lt;h1 id=&#34;107-工程实践与跨语言对照&#34;&gt;10.7 工程实践与跨语言对照&lt;/h1&gt;&#xA;&lt;p&gt;前面几节把 channel 的内部结构、收发路径与 select 的实现讲透了。读懂了机制，&#xA;随之而来的是一个更难、也更工程的问题：什么时候该用 channel，什么时候不该用。&#xA;Go 的宣传语「Do not communicate by sharing memory; instead, share memory by communicating」&#xA;容易被读成「凡共享状态皆应走 channel」，但这并非作者的本意。这一节把这句话还原成一条&#xA;可操作的判别规则，对照同步原语章（&lt;a href=&#34;../../ch11sync/readme&#34;&gt;第 11 章&lt;/a&gt;）里更轻的工具，&#xA;再把 Go 的选择放进 CSP 家族的谱系里看，理解它在设计空间中的具体坐标。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1071-channel-不是万能锤&#34;&gt;10.7.1 channel 不是万能锤&lt;/h2&gt;&#xA;&lt;p&gt;2018 年 GopherCon 上，Go 团队的 Bryan C. Mills 做了一场题为「Rethinking Classical&#xA;Concurrency Patterns」的演讲。他逐个检视了教科书里常见的并发范式，结论是：其中相当一部分&#xA;若用 channel 实现，反而比用 &lt;code&gt;sync&lt;/code&gt; 包里的原语更难写对、更慢。&lt;/p&gt;&#xA;&lt;p&gt;第一个例子是「用 channel 模拟条件变量」。一个常见的写法是用一个带缓冲的 channel 当作&#xA;「信号槽」，发送代表通知、接收代表等待：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 反例：用 channel 当条件变量，看似优雅，边界全是坑&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Cond&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;chan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Wait&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;c&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{}{}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 没有等待者时会阻塞&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;它在「恰好一个等待者、恰好一个通知者」时能工作，可一旦等待者数目不定，问题就来了：&#xA;&lt;code&gt;Signal&lt;/code&gt; 在无人等待时会阻塞或丢失信号，&lt;code&gt;Broadcast&lt;/code&gt;（唤醒全部等待者）无法表达，&#xA;而条件谓词的重新检查（被唤醒后必须重新判断条件是否真的满足）也没有着落。&#xA;正确的工具是 &lt;code&gt;sync.Cond&lt;/code&gt;（&lt;a href=&#34;../../ch11sync/cond&#34;&gt;11.4&lt;/a&gt;），它的 &lt;code&gt;Wait&lt;/code&gt; 在唤醒后让调用者&#xA;回到循环里复查谓词，&lt;code&gt;Broadcast&lt;/code&gt; 一次唤醒所有等待者，这些语义 channel 都不天然具备。&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.1 共享内存式同步模式</title>
      <link>/zh-cn/part3concurrency/ch11sync/basic/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch11sync/basic/</guid>
      <description>&lt;h1 id=&#34;111-共享内存式同步模式&#34;&gt;11.1 共享内存式同步模式&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;程序的构造可以用更简单的基础原语来表达，这一事实是一个有力的保障：这些原语所包含的内容，&#xA;能与程序语言的其余部分在逻辑上保持一致。&#xA;——  C. A. R. Hoare&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;并发编程的全部困难，可以浓缩成一句话：多个执行单元要在同一份状态上协作，而它们各自推进的&#xA;次序无法预知。如何让这种协作既正确又高效，半个多世纪以来分化出两条大的传统。本章讲的是其中&#xA;一条，共享内存。在拆解 Go 标准库 &lt;code&gt;sync&lt;/code&gt; 与 &lt;code&gt;sync/atomic&lt;/code&gt; 里那些具体原语之前，本节先把这两条&#xA;传统放在一起辨认清楚，说明它们为何是对偶的，再交代 Go 在其间所站的位置。读懂了这层取舍，&#xA;后面 &lt;a href=&#34;.././mutex&#34;&gt;11.2&lt;/a&gt; 到 &lt;a href=&#34;.././mem&#34;&gt;11.9&lt;/a&gt; 各节就不再是孤立的 API，而是同一种设计哲学落到&#xA;不同场景上的样子。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1111-并发的两大传统&#34;&gt;11.1.1 并发的两大传统&lt;/h2&gt;&#xA;&lt;p&gt;并发编程历史上有两条大的传统。一条是&lt;strong&gt;共享内存&lt;/strong&gt;：线程共享地址空间，靠互斥锁、信号量、条件&#xA;变量等手段协调对共享状态的访问。Dijkstra 1965 年提出的信号量与 Hoare 1974 年提出的监管程&#xA;（monitor）是它的奠基石，它也是 C/C++、Java、以及几乎所有操作系统内核的主流路数。另一条是&#xA;&lt;strong&gt;消息传递&lt;/strong&gt;：执行单元不共享状态，只通过收发消息协调。Hoare 的 CSP（&lt;a href=&#34;/zh-cn/part3concurrency/ch10chan/&#34;&gt;10.1&lt;/a&gt;）与&#xA;Hewitt 的 Actor 模型是它的两支，Erlang、occam 是它的代表。&lt;/p&gt;&#xA;&lt;p&gt;两条传统的分野，落在「状态归谁所有」这一点上。共享内存里状态是公有的，谁都能碰，于是必须靠&#xA;锁划出临界区，规定同一时刻只许一个执行单元进入；消息传递里状态是私有的，别人想读写只能发一封&#xA;信请它代劳，协调由信道的收发时序天然完成。&lt;/p&gt;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    subgraph SM[&amp;#34;共享内存&amp;#34;]&#xA;        direction TB&#xA;        T1[&amp;#34;线程 1&amp;#34;] --&amp;gt; L[&amp;#34;锁 / 临界区&amp;#34;]&#xA;        T2[&amp;#34;线程 2&amp;#34;] --&amp;gt; L&#xA;        L --&amp;gt; S[&amp;#34;共享状态&amp;#34;]&#xA;    end&#xA;    subgraph MP[&amp;#34;消息传递&amp;#34;]&#xA;        direction TB&#xA;        A1[&amp;#34;执行单元 1&amp;#34;] --&amp;gt;|&amp;#34;消息&amp;#34;| CH[&amp;#34;信道&amp;#34;]&#xA;        CH --&amp;gt;|&amp;#34;消息&amp;#34;| A2[&amp;#34;执行单元 2&amp;lt;br/&amp;gt;(私有状态)&amp;#34;]&#xA;    end&lt;/pre&gt;&#xA;&lt;p&gt;值得一提的是，两者并非非此即彼。Dijkstra 当年用信号量解的「生产者与消费者」「哲学家就餐」&#xA;等问题，今天用 channel 同样能写；反过来，channel 的内部实现本身就是一段被互斥锁保护的共享&#xA;缓冲（&lt;a href=&#34;/zh-cn/part3concurrency/ch10chan/&#34;&gt;10&lt;/a&gt;）。这种「换一种说法就能从一边翻译到另一边」的观感并非错觉，下一小节&#xA;会看到，它早在 1979 年就被证明是一条普遍规律。&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.2 互斥锁</title>
      <link>/zh-cn/part3concurrency/ch11sync/mutex/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch11sync/mutex/</guid>
      <description>&lt;h1 id=&#34;112-互斥锁&#34;&gt;11.2 互斥锁&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;sync.Mutex&lt;/code&gt; 是最朴素的同步原语：同一时刻只让一个 goroutine 进入临界区。朴素的接口之下，它要在&#xA;两个互相拉扯的目标间反复权衡，&lt;strong&gt;吞吐&lt;/strong&gt;（让锁尽快被某人拿走，别让 CPU 闲着）与&lt;strong&gt;公平&lt;/strong&gt;（别让某个&#xA;倒霉的等待者永远排不到队）。这两者无法同时拉满：把锁严格按到达次序移交，最公平，却要为每次交接&#xA;付一次上下文切换；放任刚醒来的与正在跑的自由竞争，最快，却可能让某个等待者一次次落空。这一节&#xA;先把互斥这个古老问题的理论与硬件地基铺开，再看 Go 的 mutex 如何在这条钢丝上行走。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1121-互斥问题与它的硬件地基&#34;&gt;11.2.1 互斥问题与它的硬件地基&lt;/h2&gt;&#xA;&lt;p&gt;互斥是并发理论最早的课题之一。Dijkstra 于 1965 年形式化了「互斥问题」，并给出第一个不借助任何&#xA;硬件特殊指令、仅凭普通读写就能让多个进程轮流进入临界区的软件解。其后 Lamport 于 1974 年的&#xA;&lt;strong&gt;面包店算法&lt;/strong&gt;（bakery algorithm）把这条路走到了一个漂亮的终点：每个想进临界区的线程先「取号」，&#xA;再按号码从小到大依次进入，既保证互斥，又保证先到先得的公平排队，而全程只用普通读写，不需要任何&#xA;原子读改写指令。&lt;/p&gt;&#xA;&lt;p&gt;纯软件解在理论上自足，工程上却昂贵：面包店算法要遍历所有线程的号码，开销随线程数增长，且严重&#xA;依赖顺序一致的内存模型（&lt;a href=&#34;.././mem&#34;&gt;11.9&lt;/a&gt;），在现代弱序硬件上还得另加屏障。因此现代锁不再走纯软件&#xA;这条路，而是直接站在硬件提供的&lt;strong&gt;原子读改写&lt;/strong&gt;指令之上，比较并交换（compare-and-swap, CAS）、&#xA;取后加（fetch-and-add）等（&lt;a href=&#34;.././atomic&#34;&gt;11.3&lt;/a&gt;）。一条 CAS 就能原子地完成面包店算法要好几步才能&#xA;模拟的事，地基一换，上层设计随之改观。&lt;/p&gt;&#xA;&lt;p&gt;围绕这些原语，锁长出了一个谱系，理解它有助于看清 Go 的 mutex 站在哪里。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;自旋锁&lt;/strong&gt;（spin lock）最简单：用一个 CAS 反复试，抢到就进，抢不到就原地空转再试。它在低竞争&#xA;下近乎零成本，但高竞争下是灾难：多个核心争同一个锁变量，每次 CAS 都让那条缓存行在核心之间&#xA;来回失效、重取，称为&lt;strong&gt;缓存行弹跳&lt;/strong&gt;（cache-line bouncing），竞争者越多越糟。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;票号锁&lt;/strong&gt;（ticket lock）用 fetch-and-add 发号，再让每个线程自旋等自己的号被叫到，由此恢复了&#xA;FIFO 公平。但所有等待者仍然自旋在同一个「当前服务号」变量上，缓存行弹跳的老问题并未根除。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;MCS 锁&lt;/strong&gt;（Mellor-Crummey 与 Scott，1991）是高扩展性锁的经典之作。它让每个等待者把自己挂进&#xA;一条&lt;strong&gt;显式队列&lt;/strong&gt;，并各自自旋在&lt;strong&gt;自己的&lt;/strong&gt;本地变量上，前一个持锁者释放时只去写后继者的那个本地&#xA;变量。如此一来，无论多少竞争者，每次交接只触动一条缓存行，竞争下的缓存流量降到常数。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以上都是「忙等」一脉，等待者占着 CPU 空转。另一条线把「睡眠等待」做廉价。早年线程要阻塞只能&#xA;陷入内核，即便锁根本没有竞争也要付系统调用的代价。Linux 的 &lt;strong&gt;futex&lt;/strong&gt;（fast userspace mutex，&#xA;Franke、Russell 与 Kirkwood，2002）解决了这个痛点：无竞争时加解锁纯在用户态用一次原子操作完成，&#xA;只有真要阻塞或唤醒等待者时，才带着那个用户态地址陷入内核去排队。几乎所有现代用户态锁，包括 Go&#xA;的 mutex，都踩在 futex（及各平台的等价物）这块「用户态快、内核态兜底」的地基上。Go 自己不直接&#xA;调 futex，而是由运行时的信号量（&lt;code&gt;runtime_SemacquireMutex&lt;/code&gt; / &lt;code&gt;runtime_Semrelease&lt;/code&gt;）封装各平台的&#xA;底层阻塞原语，对上层呈现统一接口。&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.3 原子操作</title>
      <link>/zh-cn/part3concurrency/ch11sync/atomic/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch11sync/atomic/</guid>
      <description>&lt;h1 id=&#34;113-原子操作&#34;&gt;11.3 原子操作&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;sync/atomic&lt;/code&gt; 是 Go 同步原语里最贴近硬件的一层。互斥锁（&lt;a href=&#34;.././mutex&#34;&gt;11.1&lt;/a&gt;）、channel&#xA;（&lt;a href=&#34;../ch08channel&#34;&gt;8&lt;/a&gt;）这些更高层的工具，内部都建立在原子操作之上。它提供「不可分割」的读写与&#xA;读改写：一个原子操作要么完整发生、要么没发生，中间不会被别的 goroutine 看到一半。但原子操作的&#xA;意义远不止「防撕裂」。它是无锁编程的地基，而无锁编程背后，有一套关于「哪些原语能做什么」的&#xA;深刻理论，那是本节真正想讲清的东西。&lt;/p&gt;&#xA;&lt;p&gt;本节先把基本操作摆出来，落到 CAS 这块基石；再退后一步，借 Herlihy 的共识层级回答「为什么是&#xA;CAS」；接着进入无锁的暗礁 ABA 与内存回收，说明 Go 为何把硬的无锁结构留在运行时内部；最后回到&#xA;工程，讲 &lt;code&gt;sync/atomic&lt;/code&gt; 的顺序一致承诺，以及 Go 1.19 那次把「该用原子访问」编进类型的演进。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1131-几种基本操作&#34;&gt;11.3.1 几种基本操作&lt;/h2&gt;&#xA;&lt;p&gt;原子操作的家族不大：&lt;code&gt;Load&lt;/code&gt;（原子读）、&lt;code&gt;Store&lt;/code&gt;（原子写）、&lt;code&gt;Add&lt;/code&gt;（原子增减）、&lt;code&gt;Swap&lt;/code&gt;（原子交换）、&#xA;&lt;code&gt;CompareAndSwap&lt;/code&gt;（比较并交换，CAS），以及 Go 1.23 起补齐的 &lt;code&gt;And&lt;/code&gt;/&lt;code&gt;Or&lt;/code&gt;（原子位运算）。它们都落到&#xA;单条带 &lt;code&gt;LOCK&lt;/code&gt; 前缀的 CPU 指令上：x86 上 CAS 是 &lt;code&gt;LOCK CMPXCHG&lt;/code&gt;，ARMv8 上是 &lt;code&gt;LDXR&lt;/code&gt;/&lt;code&gt;STXR&lt;/code&gt; 的&#xA;load-linked / store-conditional 重试对，加法是 &lt;code&gt;LOCK XADD&lt;/code&gt;。换言之，原子性的最终保证来自硬件，&#xA;&lt;code&gt;sync/atomic&lt;/code&gt; 只是把这些指令包装成可移植的 Go 函数。&lt;/p&gt;&#xA;&lt;p&gt;其中 CAS 是无锁编程的基石。它原子地完成「如果当前值还是我以为的那个，就改成新值，否则什么都&#xA;不做并告诉我失败」：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// CompareAndSwap 的语义（伪代码，整体不可分割地执行）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;CompareAndSwapInt64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;old&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;swapped&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;old&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;为什么单单一个 CAS 就够撑起无锁？因为几乎所有无锁算法都能写成同一种骨架，一个&lt;strong&gt;重试循环&lt;/strong&gt;：&#xA;读出当前值，据它算出想要的新值，再用 CAS 把它写回去；只有「这期间没人动过」时 CAS 才成功，&#xA;否则拿到的就是别人更新后的值，重来一遍即可。&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.4 条件变量</title>
      <link>/zh-cn/part3concurrency/ch11sync/cond/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch11sync/cond/</guid>
      <description>&lt;h1 id=&#34;114-条件变量&#34;&gt;11.4 条件变量&lt;/h1&gt;&#xA;&lt;p&gt;互斥锁（&lt;a href=&#34;.././mutex&#34;&gt;11.2&lt;/a&gt;）回答的是「谁能进临界区」，条件变量回答的则是另一个问题：&#xA;「一个 Goroutine 已经进了临界区，却发现现在还不能干活，它该如何等到能干活的那一刻，&#xA;同时把临界区让给别人」。生产者消费者是最常见的例子。队列满时，生产者既不能写入，&#xA;又不能一直攥着锁空转，否则消费者永远拿不到锁去腾空间，于是双方一起卡死。条件变量&#xA;&lt;code&gt;sync.Cond&lt;/code&gt; 给出的办法是：让生产者「带锁睡去」，原子地交出锁并阻塞，等消费者腾出空间后&#xA;把它唤醒，醒来时锁又回到它手上。&lt;/p&gt;&#xA;&lt;p&gt;这一节先把用法讲清楚，尤其是那条几乎所有教材都强调、却很少解释清楚来由的规矩：&#xA;&lt;code&gt;Wait&lt;/code&gt; 必须写在 &lt;code&gt;for&lt;/code&gt; 循环里。这条规矩不是 Go 的发明，它根植于 1970 年代关于「管程」&#xA;（monitor）的两种唤醒语义之争。讲清这段历史，读者就会明白为什么从 pthread 到 Java 再到 Go，&#xA;现代条件变量无一例外地要求循环复查。随后我们给出 Go 实现的速写（notifyList 的 ticket 机制、&#xA;禁止拷贝的 copyChecker），最后说明一个也许出乎意料的事实：在 Go 里 &lt;code&gt;sync.Cond&lt;/code&gt; 其实很少用，&#xA;channel 与 close 广播大多把它取代了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1141-用法wait-为何要写在-for-循环里&#34;&gt;11.4.1 用法：Wait 为何要写在 for 循环里&lt;/h2&gt;&#xA;&lt;p&gt;先看一段标准的生产者消费者。&lt;code&gt;condition&lt;/code&gt; 是受锁保护的共享状态，&lt;code&gt;cond&lt;/code&gt; 用同一把锁构造：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cond&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;NewCond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Mutex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;condition&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 消费者&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;go&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;L&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;condition&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 没有可消费的，带锁睡去&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Wait&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;condition&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 腾出了空间，唤醒一个生产者&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;L&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Unlock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 生产者&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;L&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;condition&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 队列满了，带锁睡去&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Wait&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;condition&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 有货了，唤醒一个消费者&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cond&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;L&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Unlock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Wait&lt;/code&gt; 的契约是三步合一：原子地解锁 &lt;code&gt;c.L&lt;/code&gt;、阻塞当前 Goroutine、被唤醒后重新锁上 &lt;code&gt;c.L&lt;/code&gt;&#xA;再返回。「原子地解锁并阻塞」是关键的一步，它堵死了一个否则必然出现的丢失唤醒窗口：&#xA;若先解锁、再阻塞分作两步，解锁之后阻塞之前的那一瞬，另一方完全可能改好条件并发出&#xA;&lt;code&gt;Signal&lt;/code&gt;，而此刻还没睡着的本方收不到这个信号，待它睡去便再无人来唤，于是永久错过。&#xA;&lt;code&gt;Wait&lt;/code&gt; 把解锁与入队做成一体，正是为了消除这个窗口。&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.5 同步组</title>
      <link>/zh-cn/part3concurrency/ch11sync/waitgroup/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch11sync/waitgroup/</guid>
      <description>&lt;h1 id=&#34;115-同步组&#34;&gt;11.5 同步组&lt;/h1&gt;&#xA;&lt;p&gt;前几节的互斥锁（&lt;a href=&#34;.././mutex&#34;&gt;11.2&lt;/a&gt;）与条件变量（&lt;a href=&#34;.././cond&#34;&gt;11.4&lt;/a&gt;）解决的是&#xA;「多个 Goroutine 争用同一份共享状态」的问题。本节要谈的 &lt;code&gt;sync.WaitGroup&lt;/code&gt; 解决的是另一类&#xA;问题：一个 Goroutine 派生出若干子任务，然后等它们全部完成再继续。前者是争用，后者是汇合。&#xA;这是并发编程里最常见的一种结构，称为 fork-join，而 &lt;code&gt;WaitGroup&lt;/code&gt; 正是 Go 标准库给它的答案。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1151-屏障与闩锁的谱系&#34;&gt;11.5.1 屏障与闩锁的谱系&lt;/h2&gt;&#xA;&lt;p&gt;在认识 &lt;code&gt;WaitGroup&lt;/code&gt; 的实现之前，先把它放进同步原语的谱系里，看清它解决的是哪一类问题。&#xA;等待「一组事件全部发生」这件事，并发文献里有一个专门的家族，叫屏障（barrier）与闩锁（latch）。&#xA;家族里的几位成员形态各异，Java 的 &lt;code&gt;java.util.concurrent&lt;/code&gt; 把它们分得相当清楚，正好用来定位&#xA;&lt;code&gt;WaitGroup&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;原语&lt;/th&gt;&#xA;          &lt;th&gt;形态&lt;/th&gt;&#xA;          &lt;th&gt;可复用&lt;/th&gt;&#xA;          &lt;th&gt;计数是否可动态增减&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CountDownLatch&lt;/code&gt;（Java）&lt;/td&gt;&#xA;          &lt;td&gt;一群等待者等计数减到 0，一次性&lt;/td&gt;&#xA;          &lt;td&gt;否&lt;/td&gt;&#xA;          &lt;td&gt;否，构造时定死&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;CyclicBarrier&lt;/code&gt;（Java）&lt;/td&gt;&#xA;          &lt;td&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个对等方互相等齐，到齐后自动重置&lt;/td&gt;&#xA;          &lt;td&gt;是&lt;/td&gt;&#xA;          &lt;td&gt;否，参与方数目固定&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;Phaser&lt;/code&gt;（Java）&lt;/td&gt;&#xA;          &lt;td&gt;分阶段屏障，参与方可中途注册 / 注销&lt;/td&gt;&#xA;          &lt;td&gt;是&lt;/td&gt;&#xA;          &lt;td&gt;是&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;sync.WaitGroup&lt;/code&gt;（Go）&lt;/td&gt;&#xA;          &lt;td&gt;一方等一组任务全部完成，fork-join&lt;/td&gt;&#xA;          &lt;td&gt;是（Wait 返回后）&lt;/td&gt;&#xA;          &lt;td&gt;是（Add 可正可负）&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;code&gt;WaitGroup&lt;/code&gt; 最接近 &lt;code&gt;CountDownLatch&lt;/code&gt;：都是「计数从某个正值减到 0，等待者随之放行」。区别在两处。&#xA;其一，&lt;code&gt;CountDownLatch&lt;/code&gt; 的计数在构造时定死，&lt;code&gt;WaitGroup&lt;/code&gt; 的计数靠 &lt;code&gt;Add&lt;/code&gt; 动态累加，这点更像&#xA;&lt;code&gt;Phaser&lt;/code&gt; 的注册机制。其二，&lt;code&gt;CountDownLatch&lt;/code&gt; 是一次性的，减到 0 便报废，而 &lt;code&gt;WaitGroup&lt;/code&gt;&#xA;在一轮 &lt;code&gt;Wait&lt;/code&gt; 返回后可以重新使用。&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.6 缓存池</title>
      <link>/zh-cn/part3concurrency/ch11sync/pool/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch11sync/pool/</guid>
      <description>&lt;h1 id=&#34;116-缓存池&#34;&gt;11.6 缓存池&lt;/h1&gt;&#xA;&lt;p&gt;频繁地分配又丢弃同一类临时对象，会给垃圾回收器（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/&#34;&gt;13&lt;/a&gt;）带来沉重压力。&#xA;&lt;code&gt;sync.Pool&lt;/code&gt; 提供一条出路：把用完的对象暂存起来、下次复用，而不是每次都新分配。它的典型用法是&#xA;缓冲区、序列化器这类&amp;quot;用完即弃、又反复要用&amp;quot;的临时对象。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bufPool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;New&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Buffer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bufPool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bytes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Buffer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Reset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 取回的对象可能是脏的，先重置&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ... 使用 b ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bufPool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Put&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 用完放回，供他人复用&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;New&lt;/code&gt; 是唯一需要用户提供的字段：当池里取不到对象时，由它兜底造一个。于是 &lt;code&gt;Get&lt;/code&gt; 拿到的，&#xA;要么是别人刚用完放回的旧对象，要么是 &lt;code&gt;New&lt;/code&gt; 现造的新对象，调用方无从、也不应区分二者。&#xA;本节先讲它复用对象的古老思路，再拆开它为并发与 GC 所做的三层设计：每 P 分片、victim 缓存、&#xA;以及与运行时 GC 的协同。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1161-对象复用一个古老的内存技巧&#34;&gt;11.6.1 对象复用：一个古老的内存技巧&lt;/h2&gt;&#xA;&lt;p&gt;&amp;ldquo;预先备好一批对象、反复借还而非频繁创建销毁&amp;rdquo;，是内存管理里很古老的思路,空闲列表&#xA;（free list，&lt;a href=&#34;../../../part4memory/ch12alloc/component&#34;&gt;12.2&lt;/a&gt;）、对象池（object pool）、slab&#xA;分配器（Bonwick 1994，内核用它缓存固定类型的对象）都是它的化身。其价值有二：省去重复的&#xA;分配与初始化开销;以及,对带 GC 的语言尤其重要,减少活跃对象的产生速率，从而降低 GC 频率与停顿。&lt;/p&gt;&#xA;&lt;p&gt;这第二点正是 &lt;code&gt;sync.Pool&lt;/code&gt; 文档里写的本分,&amp;ldquo;缓存已分配但暂未使用的对象以备复用，为垃圾回收器&#xA;减压&amp;rdquo;。要看清这层减压从何而来，可把 Go 的并发标记清扫（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/&#34;&gt;13&lt;/a&gt;）粗略记成&#xA;工作量与活跃对象数成正比：标记阶段要遍历可达对象、清扫阶段要回收死对象。每次 &lt;code&gt;New&lt;/code&gt; 一个临时&#xA;对象、用完即弃，都是在给堆增加一个&amp;quot;生而即死&amp;quot;的对象，抬高分配速率，进而更频繁地触发 GC、&#xA;每轮 GC 要扫的对象也更多。复用把这批对象从&amp;quot;反复生死&amp;quot;变成&amp;quot;借了又还&amp;quot;，既压低了分配速率，也&#xA;让它们不再每轮都成为 GC 的扫描与回收对象。fmt 包就是教科书式的例子：它用一个 &lt;code&gt;sync.Pool&lt;/code&gt;&#xA;维护临时输出缓冲，并发打印多时缓冲池自动变大，空闲时随 GC 收缩。它把这个古老技巧做成了&#xA;并发安全、且与 Go GC 协同的标准件。需要强调的是它的边界：池是&#xA;对 GC 压力的优化，不是托管对象生命周期的容器。一个只在某个短命对象内部维护的 free list 不该&#xA;用 &lt;code&gt;sync.Pool&lt;/code&gt;,文档明确指出，那种场景下让对象自带 free list 更划算，因为 &lt;code&gt;Pool&lt;/code&gt; 的分摊收益&#xA;建立在&amp;quot;被一个包的众多并发使用者静默共享&amp;quot;之上，单点使用摊不开它的固定开销。&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.7 并发安全散列表</title>
      <link>/zh-cn/part3concurrency/ch11sync/map/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch11sync/map/</guid>
      <description>&lt;h1 id=&#34;117-并发安全散列表&#34;&gt;11.7 并发安全散列表&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;code&gt;sync.Map&lt;/code&gt; 在 Go 1.24 经历了一次彻底的内部重写：原先的&#xA;read/dirty 双 map 设计被换成一个并发散列字典树（hash-trie）。本节先把这两代设计&#xA;各自的来由讲清楚，再把它们放回并发散列表的解法谱系里。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;语言内建的 &lt;code&gt;map&lt;/code&gt; 不是并发安全的。多个 Goroutine 在没有同步的前提下同时读写同一个 &lt;code&gt;map&lt;/code&gt;，&#xA;运行时会主动探测到并发访问并以 &lt;code&gt;fatal error: concurrent map read and map write&lt;/code&gt; 终止进程&#xA;（&lt;a href=&#34;../../../part2lang/ch05data/map&#34;&gt;5.2&lt;/a&gt; 介绍过这个检测机制，它是不可恢复的，&lt;code&gt;recover&lt;/code&gt;&#xA;拦不住）。对可用性有要求的服务，这是一处必须正面回答的设计问题：当多个 Goroutine 要共享&#xA;一张表，应当用什么结构。&lt;/p&gt;&#xA;&lt;p&gt;最朴素的答案是给一张普通 &lt;code&gt;map&lt;/code&gt; 套一把锁：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 一张普通 map 加一把读写锁，是最直接、也最常够用的并发安全表&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RWMutexMap&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mu&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RWMutex&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RWMutexMap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Load&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mu&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RLock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mu&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RUnlock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RWMutexMap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Store&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mu&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Lock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mu&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Unlock&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这套写法简单、类型安全（键值类型由你自己定），在多数场景下也足够快。它的瓶颈只在一处：&#xA;所有读都要竞争同一把锁。&lt;code&gt;RWMutex&lt;/code&gt; 允许多个读者并发持锁，但持锁本身要更新读者计数，这是一次&#xA;对共享缓存行的原子写，核数一多就成为争用点（&lt;a href=&#34;.././mutex&#34;&gt;11.2&lt;/a&gt; 讲过这件事）。&lt;code&gt;sync.Map&lt;/code&gt;&#xA;要解决的，正是这个「读多到锁本身都嫌贵」的场景。在看它怎么做之前，先把整张解法地图铺开。&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.8 上下文</title>
      <link>/zh-cn/part3concurrency/ch11sync/context/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch11sync/context/</guid>
      <description>&lt;h1 id=&#34;118-上下文&#34;&gt;11.8 上下文&lt;/h1&gt;&#xA;&lt;p&gt;一个进入服务器的请求，往往不会只在一个 goroutine 里走完。它会扇出（fan-out）成一棵&#xA;goroutine 树：查数据库的、调下游 RPC 的、读缓存的，各自再派生出更小的工作。一旦请求的发起方&#xA;不再需要结果，比如客户端断开了连接，或上游已超时，这整棵树上仍在运行的工作就成了纯粹的浪费，&#xA;占着连接、锁与内存，却没有人会去读它们的产出。于是问题变成：&lt;strong&gt;怎样把「到此为止」这个信号，&#xA;从树根一路传播到每一片叶子，让它们各自停下来。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;context&lt;/code&gt; 包就是 Go 对这个问题给出的标准答案。它把取消、截止时间与请求范围内的元数据，&#xA;沿着调用链显式传递，使一棵 goroutine 树可以被统一地取消。本节先说清它为什么是协作式而非&#xA;强制式的，再沿取消树的结构走一遍实现，最后把它放进结构化并发与跨语言的谱系里来看。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1181-为什么取消是信号而非强制&#34;&gt;11.8.1 为什么取消是信号，而非强制&lt;/h2&gt;&#xA;&lt;p&gt;最朴素的想法，是给 goroutine 一个「杀掉它」的 API。Go 偏偏没有这样的 API，这不是疏漏，&#xA;而是从前人的教训里学来的设计。&lt;/p&gt;&#xA;&lt;p&gt;Java 早年提供过 &lt;code&gt;Thread.stop()&lt;/code&gt;，能从外部强行终止一个线程。它在 JDK 1.2 就被废弃，原因写在&#xA;官方的废弃说明里：被停止的线程会立刻抛出 &lt;code&gt;ThreadDeath&lt;/code&gt;，并&lt;strong&gt;释放它当时持有的所有监视器锁&lt;/strong&gt;。&#xA;锁是用来保护一段不变式的，持锁线程被半路打断、锁却被放开，意味着其他线程会看到一个处于&#xA;中间状态、不变式被破坏的共享对象，而且毫无征兆。后来的 JDK 干脆让 &lt;code&gt;Thread.stop()&lt;/code&gt;&#xA;直接抛 &lt;code&gt;UnsupportedOperationException&lt;/code&gt;，把这扇门彻底关上。强制终止之所以危险，根子在于&#xA;外部无从知道目标此刻是否正处在某个不可被打断的临界区里。&lt;/p&gt;&#xA;&lt;p&gt;Go 由此选择了相反的路：运行时不提供「杀死一个 goroutine」的手段，取消是一个&#xA;&lt;strong&gt;信号&lt;/strong&gt;，由 goroutine 自己在安全的检查点去查看、再自行决定如何收尾。撤销、回滚、释放资源，&#xA;这些只有代码自己知道怎么做对，于是就交还给代码自己做。&lt;/p&gt;&#xA;&lt;p&gt;在有 &lt;code&gt;context&lt;/code&gt; 之前，这个信号可以用一个 channel 手写出来。关闭一个 channel 会唤醒所有在它上面&#xA;等待的接收者（&lt;a href=&#34;../../ch10chan/readme&#34;&gt;10.4&lt;/a&gt;），这正是「广播一次取消」的天然载体：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;cancel&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;chan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{})&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;go&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;chan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;go&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{}{}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;do&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 执行需要执行的操作&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cancel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 提前被取消：等已启动的工作收尾，再撤销其副作用&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;undo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 顺利结束&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 出于某些原因希望取消&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cancel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这段代码已经含着 &lt;code&gt;context&lt;/code&gt; 的全部要点：取消方只负责发信号（&lt;code&gt;close(cancel)&lt;/code&gt;），被取消方在&#xA;&lt;code&gt;select&lt;/code&gt; 里把取消信号与正常完成并列等待，收到信号后在自己选定的安全点收尾。&lt;code&gt;context&lt;/code&gt; 做的，&#xA;是把这套手写模式抽象成一个接口，并解决一个手写版没有解决的问题：当工作扇出成一棵树时，&#xA;如何让一次取消沿树&lt;strong&gt;自动&lt;/strong&gt;传播到每一个后代。&lt;/p&gt;</description>
    </item>
    <item>
      <title>11.9 内存一致模型</title>
      <link>/zh-cn/part3concurrency/ch11sync/mem/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part3concurrency/ch11sync/mem/</guid>
      <description>&lt;h1 id=&#34;119-内存一致模型&#34;&gt;11.9 内存一致模型&lt;/h1&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Go 的内存模型在 2022 年随 Go 1.19 做过一次重要修订，&#xA;本节在讲清当前模型之后，会专门交代这次修订的来龙去脉（见 &lt;a href=&#34;/zh-cn/part3concurrency/ch11sync/mem/#1198-%e8%ae%be%e8%ae%a1%e7%9a%84%e6%bc%94%e8%bf%9b&#34;&gt;11.9.8&lt;/a&gt;）。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;读者或许注意到，在此前关于 Go 运行时与编译器的讨论中，我们一直回避着一个话题：&#xA;Go 的内存模型。回避并非无故。要讲清楚它，需要并发、同步原语乃至硬件层面的铺垫，&#xA;而这些铺垫至此方才齐备。作为本章的收尾，亦是全书对 Go 同步原语与同步模式的总结，&#xA;我们在此展开这一话题，回答读者心中那个尚未解开的疑问：两个 Goroutine 同时读写同一变量时，&#xA;一方的写入在何种条件下才保证为另一方所见。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1191-内存模型的重要性&#34;&gt;11.9.1 内存模型的重要性&lt;/h2&gt;&#xA;&lt;p&gt;内存一致模型，或称内存模型，是一份契约，立于语言用户与语言自身、语言与操作系统平台、&#xA;操作系统平台与硬件平台之间。它界定并行之下读写时序得以确定的条件，回答这样一个问题：&#xA;某个共享变量是否具备足够的同步，使一个线程的写入能为另一个线程的读取所见。&lt;/p&gt;&#xA;&lt;p&gt;一份 Go 程序写成之后，要先后经过编译器的转换与优化、运行其上的操作系统或虚拟机等动态优化器，&#xA;以及 CPU 对指令流的优化，方能执行。其中任何一个环节，都可能调整某个变量读写的次序，&#xA;使其偏离程序员写下的顺序。没有内存模型作保障，便无从推演程序最终执行时是否正确。&lt;/p&gt;&#xA;&lt;p&gt;内存模型的取舍影响深远，关乎程序的可移植性与可维护性。过强的模型压缩硬件与编译器的优化空间，&#xA;压低性能上限；一种已选定强模型的硬件体系结构，又难以在不破坏兼容性的前提下转向更弱的模型，&#xA;而破坏兼容性的代价，是要求其上的程序重写源码。&lt;/p&gt;&#xA;&lt;p&gt;横跨用户、软件与硬件三方，使内存模型的设计格外棘手，至今仍是开放的研究问题。&#xA;讨论 Go 的内存模型之前，我们先了解既有的内存模型，以及软硬件之间历次立约的经验与教训。&lt;/p&gt;&#xA;&lt;p&gt;为使「顺序被调整」一事具体可见，考虑一段最小的生产者与消费者程序。它们共享 &lt;code&gt;data&lt;/code&gt; 与&#xA;&lt;code&gt;done&lt;/code&gt;，一个写入数据并置位标志，另一个等待标志置位后读取数据：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;producer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// (1)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// (2)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;consumer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// (3)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// (4)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;源代码中 (1) 写在 (2) 之前，直觉上 &lt;code&gt;consumer&lt;/code&gt; 一旦读到 &lt;code&gt;done == true&lt;/code&gt;，便应当读到&#xA;&lt;code&gt;data == 42&lt;/code&gt;。然而这段程序并不保证打印 42，也可能停在循环中不再退出。前文所说的三道优化，&#xA;在此各显其形：编译器可将无依赖的 (1) 与 (2) 互换，或将循环中对 &lt;code&gt;done&lt;/code&gt; 的读取提升至循环外，&#xA;使其反复读取同一寄存器中的旧值；CPU 以乱序方式发射指令，只为单个核心维持其自身可见的数据&#xA;依赖；写入还会先停留在核心私有的存储缓冲（store buffer）中，何时对其他核心可见，&#xA;由缓存一致性协议决定，与写入的先后未必一致。&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.1 设计原则</title>
      <link>/zh-cn/part4memory/ch12alloc/basic/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch12alloc/basic/</guid>
      <description>&lt;h1 id=&#34;121-设计原则&#34;&gt;12.1 设计原则&lt;/h1&gt;&#xA;&lt;p&gt;至此我们已看清 Go 程序如何启动、调度器如何把 goroutine 摊到操作系统线程上执行。&#xA;那些被调度的代码做的最频繁的一件事，是申请内存：每一次 &lt;code&gt;new&lt;/code&gt;、每一个逃逸到堆上的局部变量、&#xA;每一次 slice 扩容，最终都落到同一个入口 &lt;code&gt;runtime.mallocgc&lt;/code&gt;。本章讲这个入口背后的分配器，&#xA;本节先不碰它的零件，而是回答一个更靠前的问题：一个为 Go 服务的内存分配器，究竟要满足哪些&#xA;互相拉扯的目标，又是依凭哪几个判断把这些目标安顿下来的。读懂了这套取舍，后面几节&#xA;（&lt;a href=&#34;.././component&#34;&gt;12.2&lt;/a&gt;–&lt;a href=&#34;.././tinyalloc&#34;&gt;12.6&lt;/a&gt;）的结构与路径就都有了来由。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1211-四个互相拉扯的目标&#34;&gt;12.1.1 四个互相拉扯的目标&lt;/h2&gt;&#xA;&lt;p&gt;先把范围划清。本章谈的「分配」专指&lt;strong&gt;堆分配&lt;/strong&gt;，即 &lt;code&gt;runtime.mallocgc&lt;/code&gt; 经手的那部分。栈上的&#xA;局部变量随函数返回自然回收，不归分配器管；只有逃逸到堆上的对象才进入这套机制。一个对象是&#xA;留在栈上还是逃逸到堆，由编译器的逃逸分析在编译期判定：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;y&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// y 的地址被返回，逃逸到堆 → 编译器插入 newobject&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;y&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// x 不逃逸，留在栈上 → 不触发堆分配&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;f&lt;/code&gt; 中的 &lt;code&gt;y&lt;/code&gt; 会被编译器改写为一次 &lt;code&gt;runtime.newobject&lt;/code&gt; 调用（&lt;code&gt;new&lt;/code&gt; 关键字同样落到这里），&#xA;而 &lt;code&gt;newobject&lt;/code&gt; 只是带上类型信息调一下 &lt;code&gt;mallocgc&lt;/code&gt;。换言之，本章讨论的全部分配路径，入口都是&#xA;这一个函数。逃逸分析的细节属于编译器（&lt;a href=&#34;/zh-cn/part5toolchain/ch15compile/&#34;&gt;15&lt;/a&gt;），这里只需记住：&lt;strong&gt;进入分配器&#xA;的，是那些编译器判定无法安放在栈上的对象&lt;/strong&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.2 组件</title>
      <link>/zh-cn/part4memory/ch12alloc/component/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch12alloc/component/</guid>
      <description>&lt;h1 id=&#34;122-组件&#34;&gt;12.2 组件&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././basic&#34;&gt;12.1&lt;/a&gt; 说分配器是「快路径无锁、慢路径加锁」的分层结构。这一节给这套结构里的几个&#xA;核心组件命名、定位，并落到它们在 go1.26 中的实际形态：每个组件承载什么状态、为何如此设计、&#xA;以及它们如何串成一条补货链。读懂了这几样东西，后面的分配路径（&lt;a href=&#34;.././largealloc&#34;&gt;12.4&lt;/a&gt;–&#xA;&lt;a href=&#34;.././tinyalloc&#34;&gt;12.6&lt;/a&gt;）就只是「在这张图上走一遍」。&lt;/p&gt;&#xA;&lt;p&gt;为避免落回逐字段翻译源码的窠臼，下文给出的结构体都是&lt;strong&gt;裁剪后的速写&lt;/strong&gt;：只保留与设计相关的&#xA;字段，并在注释里说明它为何存在。完整定义可对照 &lt;code&gt;runtime/mheap.go&lt;/code&gt;、&lt;code&gt;mcache.go&lt;/code&gt;、&lt;code&gt;mcentral.go&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1221-自由表一切的底层手法&#34;&gt;12.2.1 自由表：一切的底层手法&lt;/h2&gt;&#xA;&lt;p&gt;在认识各组件之前，先认识它们共用的一个底层手法，&lt;strong&gt;自由表&lt;/strong&gt;（free list）。运行时里大量&amp;quot;固定&#xA;大小对象&amp;quot;（mcache、mspan、各类元数据）都由一个叫 &lt;code&gt;fixalloc&lt;/code&gt; 的固定大小分配器管理，它的思想&#xA;极简：把一批未分配的内存块用指针串成一条链，&lt;strong&gt;每块内存的头部恰好用作指向下一块的指针&lt;/strong&gt;。&#xA;分配就是摘下链头，回收就是把块插回链头，都是 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的指针操作：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// fixalloc：固定大小对象的自由表分配器（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fixalloc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 每个对象的固定大小&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mlink&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 自由表：空闲块串成的链&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;chunk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 当前向操作系统批发来的大块的游标&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nchunk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 大块中剩余的字节&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fixalloc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;alloc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 自由表里有可复用的块&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 摘下链头&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 自由表空了：从批发来的 chunk 里切一块；chunk 也用尽就向操作系统再批发&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nchunk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;chunk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;persistentalloc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_FixAllocChunk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stat&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nchunk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_FixAllocChunk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;chunk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;chunk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nchunk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&amp;ldquo;用空闲块自身的头部存下一块的指针&amp;quot;是自由表的精髓,它不需要额外的元数据数组来记录空闲位置，&#xA;空间开销为零。这个手法会在下文反复出现：mspan 内部用它串空闲对象槽，mcache 用它缓存 stack，&#xA;理解了它，分配器的许多角落就都通了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.3 初始化</title>
      <link>/zh-cn/part4memory/ch12alloc/init/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch12alloc/init/</guid>
      <description>&lt;h1 id=&#34;123-初始化&#34;&gt;12.3 初始化&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././component&#34;&gt;12.2&lt;/a&gt; 把分配器拆成了 mcache、mcentral、mheap、arena 几样零件，但那是一张&#xA;静态的图。这些零件并非凭空就位，它们要在程序启动之初被一次性安放好。本节回答的问题是：&#xA;当 &lt;code&gt;main&lt;/code&gt; 还没运行、第一次 &lt;code&gt;new&lt;/code&gt; 还没发生时，运行时究竟为分配器铺好了哪些基础设施。&lt;/p&gt;&#xA;&lt;p&gt;读懂初始化，关键不在于逐行复述 &lt;code&gt;mallocinit&lt;/code&gt;，而在于看清两件被它确立下来、此后再不更改的&#xA;事实：其一，&lt;strong&gt;地址空间是怎样被组织成 arena 的&lt;/strong&gt;，这决定了运行时如何从任意一个堆地址反查出&#xA;「它属于哪个 span、里面是不是指针、是否还存活」，是垃圾回收（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/&#34;&gt;13&lt;/a&gt;）赖以工作的地基；&#xA;其二，&lt;strong&gt;「预留地址」与「提交物理内存」是两件事&lt;/strong&gt;，这解释了为何一个 Go 进程的虚拟内存（VIRT）&#xA;常常远大于其常驻内存（RES），而这并不是泄漏。&lt;/p&gt;&#xA;&lt;p&gt;分配器是除执行栈外最先完成初始化的子系统之一，由调度器引导阶段调用 &lt;code&gt;mallocinit&lt;/code&gt;&#xA;（&lt;a href=&#34;../../../part1overview/ch03life/boot&#34;&gt;3.5&lt;/a&gt;）。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1231-mallocinit启动时铺好地基&#34;&gt;12.3.1 mallocinit：启动时铺好地基&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;mallocinit&lt;/code&gt; 做三类事：一批关于常量的自检（编译期算出的 arena 尺寸、位图字数、物理页大小&#xA;必须自洽），初始化全局堆 &lt;code&gt;mheap_&lt;/code&gt;，以及为 64 位地址空间播下一组「arena 增长提示」。裁剪后的&#xA;速写如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;mallocinit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 1. 自检：编译期常量必须自洽。例如堆位图字数须为 2 的幂（取模寻址要用），&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;//    物理页大小须落在 [minPhysPageSize, maxPhysPageSize] 且为 2 的幂。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;heapArenaBitmapWords&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;heapArenaBitmapWords&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;throw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;heapArenaBitmapWords not a power of 2&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;physPageSize&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;throw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;failed to get system page size&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ... 更多关于 physHugePageSize、size class 边界的检查&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 2. 初始化全局堆，并为引导线程分配第一个 mcache。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mheap_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;init&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mcache0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;allocmcache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 3. 在 64 位机器上播下 arena 增长提示，从地址空间中部开始。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;goarch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PtrSize&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0x7f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;switch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GOARCH&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;arm64&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GOOS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;darwin&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;40&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;uintptrMask&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0x0013&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;28&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;40&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;uintptrMask&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0x00c0&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;arenaHint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mheap_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;arenaHintAlloc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;alloc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mheap_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;arenaHints&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mheap_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;arenaHints&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;那串自检不是仪式。分配器的许多快路径靠位运算寻址，前提是这些尺寸恰为 2 的幂、彼此整除；&#xA;若某次移植或改尺寸破坏了前提，与其让程序在运行时给出难解的越界，不如在启动时当场 &lt;code&gt;throw&lt;/code&gt;。&#xA;这是运行时代码的一种笔法：把「本应恒真」的不变量写成启动期断言，把错误挡在最前面。&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.4 大对象分配</title>
      <link>/zh-cn/part4memory/ch12alloc/largealloc/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch12alloc/largealloc/</guid>
      <description>&lt;h1 id=&#34;124-大对象分配&#34;&gt;12.4 大对象分配&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././component&#34;&gt;12.2&lt;/a&gt; 的分配层级是为「小而频繁」的对象造的：每 P 一份的 mcache、按尺寸类&#xA;共享的 mcentral、全局的 mheap，三层缓存把绝大多数分配收敛成几条无锁位运算。可这套精巧的&#xA;机器有一个隐含前提，对象小到能被尺寸类装下。一旦对象超过 &lt;code&gt;maxSmallSize&lt;/code&gt;（go1.26 中为&#xA;32768 字节，即 32KB），它就装不进任何尺寸类的槽位，整条补货链对它失去意义。&lt;/p&gt;&#xA;&lt;p&gt;大对象（large object）走的是另一条路：跳过 mcache 与 mcentral，&lt;strong&gt;直接向 mheap 按页申请&lt;/strong&gt;&#xA;一段连续内存，为它单独造一个 span。这条路在代码上比小对象简短得多，但简短不等于廉价。&#xA;本节讲清楚它为何被设计成「绕开缓存」，这条路具体怎么走，以及绕开缓存在工程上要付出什么。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1241-为什么大对象不进缓存&#34;&gt;12.4.1 为什么大对象不进缓存&lt;/h2&gt;&#xA;&lt;p&gt;缓存之所以划算，靠的是两个假设：被缓存的东西&lt;strong&gt;频繁复用&lt;/strong&gt;，且&lt;strong&gt;尺寸整齐&lt;/strong&gt;到可以预先按类&#xA;备货。小对象两条都满足，于是 mcache 为每个尺寸类常备一个 span，命中率高、摊销下来近乎免费。&#xA;大对象两条都不满足。&lt;/p&gt;&#xA;&lt;p&gt;它们低频。一个程序里 32KB 以上的分配，数量级远小于小对象，多是大缓冲区、大切片、&#xA;大哈希表的底层数组。为低频对象维护每 P 缓存，备的货大半时间闲置，命中率却上不去，&#xA;缓存反而成了纯粹的内存浪费。&lt;/p&gt;&#xA;&lt;p&gt;它们尺寸离散。小对象被归进 68 个尺寸类，是因为这个区间内的尺寸足够密集，量化到固定档位的&#xA;浪费可控（最坏约 12.5%，见 &lt;a href=&#34;.././basic&#34;&gt;12.1&lt;/a&gt;）。大对象的尺寸跨度从 32KB 到数百 MB，&#xA;若仍按尺寸类备货，类数会爆炸，每类又难得复用一次。与其按类缓存，不如来一个算一个，&#xA;按实际页数现切。&lt;/p&gt;&#xA;&lt;p&gt;这正是分层分配器一以贯之的取舍：&lt;strong&gt;把热路径打磨到极致，让冷路径保持简单&lt;/strong&gt;。小对象是热路径，&#xA;值得用三层缓存和位图换速度；大对象是冷路径，多写的缓存逻辑省不下多少时间，却凭空添了内存&#xA;与复杂度。于是 go1.26 干脆为它另开一个入口 &lt;code&gt;mallocgcLarge&lt;/code&gt;，与小对象的&#xA;&lt;code&gt;mallocgcSmall*&lt;/code&gt;、微对象的 &lt;code&gt;mallocgcTiny&lt;/code&gt; 在 &lt;code&gt;mallocgc&lt;/code&gt; 里就按尺寸分流（见&#xA;&lt;a href=&#34;.././smallalloc&#34;&gt;12.5&lt;/a&gt;），各走各的代码，互不拖累。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1242-直接向堆要页&#34;&gt;12.4.2 直接向堆要页&lt;/h2&gt;&#xA;&lt;p&gt;大对象分配的主干很短。&lt;code&gt;mallocgcLarge&lt;/code&gt; 拿到 mcache 只是为了借用它的统计与采样字段，&#xA;真正要 span 的活交给 &lt;code&gt;mcache.allocLarge&lt;/code&gt;。后者做两件事：把字节数向上取整成页数，&#xA;再向 mheap 要这么多页：&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.5 小对象分配</title>
      <link>/zh-cn/part4memory/ch12alloc/smallalloc/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch12alloc/smallalloc/</guid>
      <description>&lt;h1 id=&#34;125-小对象分配&#34;&gt;12.5 小对象分配&lt;/h1&gt;&#xA;&lt;p&gt;小对象指尺寸在 16B 至 32KB 之间的对象（go1.26 中 &lt;code&gt;maxSmallSize = 32768&lt;/code&gt;）。它们是 Go 程序里&#xA;最常见的一类分配：一个结构体、一个不算太大的切片底层数组、一条字符串拼接的结果，绝大多数都落在&#xA;这个区间。分配器为它们设计的路径，是 &lt;a href=&#34;.././component&#34;&gt;12.2&lt;/a&gt; 那套 mcache → mcentral → mheap&#xA;层级的主舞台，也是整套内存分配器「快路径无锁」设计（&lt;a href=&#34;.././basic&#34;&gt;12.1&lt;/a&gt;）兑现性能承诺的地方。&lt;/p&gt;&#xA;&lt;p&gt;这一节把一次小对象分配拆成三步来读：先把请求的尺寸&lt;strong&gt;取整到一个尺寸类&lt;/strong&gt;，再从当前 P 的 &lt;strong&gt;mcache&#xA;无锁取槽&lt;/strong&gt;，取不到才落入&lt;strong&gt;加锁补货&lt;/strong&gt;的慢路径。读完会发现，常态下的小对象分配不过是「查一次表、&#xA;扫一次位、推一下游标」，慢路径只是为这条快路径兜底。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1251-第一步把尺寸取整到尺寸类&#34;&gt;12.5.1 第一步：把尺寸取整到尺寸类&lt;/h2&gt;&#xA;&lt;p&gt;分配器不会为「恰好 37 字节」单独管理一种内存块。它把 16B 至 32KB 的尺寸空间划成 68 个&lt;strong&gt;尺寸类&lt;/strong&gt;&#xA;（size class，&lt;a href=&#34;.././basic&#34;&gt;12.1&lt;/a&gt;），每个尺寸类对应一个固定的对象大小（8、16、24、32、48……&#xA;直到 32768），分配时把请求向上取整到最近的尺寸类。这一步要做到 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(1)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，靠的是两张预先生成&#xA;（由 &lt;code&gt;mksizeclasses.go&lt;/code&gt; 算出）的查找表：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 把请求尺寸映射到尺寸类，再取出该类的实际对象大小（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sizeclass&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SmallSizeMax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// SmallSizeMax = 1024&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sizeclass&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SizeToSizeClass8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;divRoundUp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SmallSizeDiv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 步长 8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sizeclass&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SizeToSizeClass128&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;divRoundUp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SmallSizeMax&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;LargeSizeDiv&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 步长 128&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SizeClassToSize&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sizeclass&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 取整后的真实分配大小&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;spc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;makeSpanClass&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sizeclass&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;noscan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 尺寸类 + 是否含指针 = spanClass&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;为何分成两张表？尺寸类在小区间排得密、大区间排得疏（1024 以下按 8 字节一档，以上按 128 字节&#xA;一档），用两个不同步长（&lt;code&gt;SmallSizeDiv = 8&lt;/code&gt;、&lt;code&gt;LargeSizeDiv = 128&lt;/code&gt;）的表，能把映射压成两次&#xA;数组下标读取，避免任何循环或除法。&lt;code&gt;divRoundUp&lt;/code&gt; 也只是一次乘加，不走真正的除法指令。&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.6 微对象分配</title>
      <link>/zh-cn/part4memory/ch12alloc/tinyalloc/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch12alloc/tinyalloc/</guid>
      <description>&lt;h1 id=&#34;126-微对象分配&#34;&gt;12.6 微对象分配&lt;/h1&gt;&#xA;&lt;p&gt;前两节走完了分配器的两端。大对象（&lt;a href=&#34;.././largealloc&#34;&gt;12.4&lt;/a&gt;）绕开缓存，直接向 mheap 按页要内存；&#xA;小对象（&lt;a href=&#34;.././smallalloc&#34;&gt;12.5&lt;/a&gt;）按尺寸类从每 P 的 mcache 里摘一个等大槽位。这一节补上最后、&#xA;也是最不起眼的一类对象，&lt;strong&gt;微对象&lt;/strong&gt;（tiny object）：小于 16 byte 且不含指针的那些。它们数量极多，&#xA;单个又极小，若也按尺寸类各占一个槽位，浪费会大得惊人。Go 为它们单设了一条路径，把多个微对象&#xA;&lt;strong&gt;拼进同一个块&lt;/strong&gt;，用一处简单的「碰撞指针」（bump pointer）手法换来可观的内存节省。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1261-问题把一个-bool-塞进-16-byte-的槽位&#34;&gt;12.6.1 问题：把一个 &lt;code&gt;bool&lt;/code&gt; 塞进 16 byte 的槽位&lt;/h2&gt;&#xA;&lt;p&gt;回顾小对象分配的代价。尺寸类是离散的，分配 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个字节，实际占用的是「不小于 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的最近一档&#xA;尺寸类」（&lt;a href=&#34;.././basic&#34;&gt;12.1&lt;/a&gt;）。最小的几档尺寸类是 8、16、24 byte。于是一个 1 byte 的 &lt;code&gt;bool&lt;/code&gt;&#xA;逃逸到堆上，要占满一个 8 byte 槽位，浪费 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;7&lt;/mn&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;/&lt;/mi&gt;&lt;mn&gt;8&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;7/8&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;7/8&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;；一个 5 byte 的字符串片段同样占 8 byte。&#xA;对单次分配，这点内部碎片无关痛痒；可微对象在真实程序里出奇地常见：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;[]byte&lt;/code&gt;、&lt;code&gt;string&lt;/code&gt; 在拼接、切分时产生的短片段；&lt;/li&gt;&#xA;&lt;li&gt;逃逸到堆上的标量临时量，如 &lt;code&gt;interface{}&lt;/code&gt; 装箱一个小整数；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;for range&lt;/code&gt; 取地址、闭包捕获等场合下被迫上堆的小变量。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这些对象每一个都不到 8 byte，却各占一个槽位。把它们累加起来，浪费的就不再是零头。若能把若干&#xA;微对象&lt;strong&gt;并排塞进一个槽位&lt;/strong&gt;，让它们共享同一段内存，节省便立竿见影。这正是微对象分配器要做的事。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1262-微对象分配器一个块一个偏移&#34;&gt;12.6.2 微对象分配器：一个块，一个偏移&lt;/h2&gt;&#xA;&lt;p&gt;手法本身朴素。mcache 里为微对象留了两个字段（&lt;a href=&#34;.././component&#34;&gt;12.2&lt;/a&gt;）：当前正在填充的 16 byte&#xA;块的起址 &lt;code&gt;tiny&lt;/code&gt;，以及块内已用到的偏移 &lt;code&gt;tinyoffset&lt;/code&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.7 页分配器</title>
      <link>/zh-cn/part4memory/ch12alloc/pagealloc/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch12alloc/pagealloc/</guid>
      <description>&lt;h1 id=&#34;127-页分配器&#34;&gt;12.7 页分配器&lt;/h1&gt;&#xA;&lt;p&gt;mheap（&lt;a href=&#34;.././component&#34;&gt;12.2&lt;/a&gt;）之下，回答「哪些页空闲、哪些已用」的，是页分配器。它是整个&#xA;分配器的地基：mcentral 缺 span 时向 mheap 要新页，大对象（&lt;a href=&#34;.././largealloc&#34;&gt;12.4&lt;/a&gt;）直接按页申请，&#xA;所有 span 占的页最终都从这里切出来。页分配器还要把长期闲置的页交还操作系统。这两件事，一头连着&#xA;分配的速度，一头连着进程的常驻内存，恰是 Go 内存系统里那条最持久的张力。&lt;/p&gt;&#xA;&lt;p&gt;本节先把页分配器要解的问题摆清楚，再看早期 treap 的设计与软肋，然后展开 Go 1.14 那次把它换成&#xA;位图加基数树的重写，这是「换数据结构换性能」最干净的一个例子。最后看两条与之共生的机制：每 P&#xA;的页缓存，以及把内存还给操作系统的 scavenging，连同 &lt;code&gt;GOMEMLIMIT&lt;/code&gt; 这个软上限。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1271-问题快速找一段连续空闲页&#34;&gt;12.7.1 问题：快速找一段连续空闲页&lt;/h2&gt;&#xA;&lt;p&gt;页分配器要回答的核心问题只有一句：在巨大的堆地址空间里，快速找到一段足够长的连续空闲页。&#xA;「连续」是硬约束，一个 span 占的是物理上相邻的若干页（&lt;a href=&#34;.././component&#34;&gt;12.2&lt;/a&gt;），不能东拼西凑。&#xA;围绕这一句，还有几条同样要满足的要求：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;分配与归还都要快，且归还的页要能被后续分配重新利用；&lt;/li&gt;&#xA;&lt;li&gt;多个 P 会同时申请页，数据结构不能被一把全局锁锁死；&lt;/li&gt;&#xA;&lt;li&gt;元数据本身的内存开销要可控，堆可以很大（64 位上地址空间按 TiB 计）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;把堆想象成一条极长的纸带，每一页是带上的一格。分配是「找一段够长的连续空格、涂黑」，归还是&#xA;「把某段涂回空白」。难点全在「够长的连续」与「快」这两个词的合取上：单看某一格是否空闲很容易，&#xA;难的是不逐格扫描就知道「从这里往后有没有连着 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 格的空白」。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1272-早期的-treap-与它的软肋&#34;&gt;12.7.2 早期的 treap 与它的软肋&lt;/h2&gt;&#xA;&lt;p&gt;Go 1.14 之前，页分配器用一棵 treap（树堆）组织空闲的页区间，按区间的起始地址作二叉搜索树的键、&#xA;按一个随机优先级维持堆序，从而在期望意义上保持平衡。每个节点是一段连续空闲页。查找一段长度为&#xA;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的空闲区间、分配后的区间分裂、归还后的相邻区间合并，都借树的结构完成，单次操作的期望复杂度&#xA;是 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;log&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(\log n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mop&#34;&gt;lo&lt;span style=&#34;margin-right:0.01389em;&#34;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 为空闲区间的个数。&lt;/p&gt;&#xA;&lt;p&gt;这套设计能用，但在大堆、高并发下暴露出三处软肋。其一，&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;O&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;log&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;O(\log n)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02778em;&#34;&gt;O&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mop&#34;&gt;lo&lt;span style=&#34;margin-right:0.01389em;&#34;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 看着不坏，可树越大常数越沉，&#xA;每次查找都要从根往下走若干层。其二，treap 是指针链接的动态结构，每跳一层就是一次可能未命中缓存的&#xA;指针追逐（pointer chasing），在现代 CPU 上，一次 LLC 未命中的代价抵得上上百条算术指令，树的随机&#xA;内存布局让缓存几乎帮不上忙。其三，也是最致命的，整棵树由一把全局锁保护，任何一个 P 申请或归还&#xA;页都要先抢到这把锁，核数一多，锁争用直接成为吞吐瓶颈。换言之，treap 的问题不在算法复杂度的&#xA;渐进阶，而在它的内存访问模式与并发模型，与多核硬件不合。&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.8 内存统计</title>
      <link>/zh-cn/part4memory/ch12alloc/mstats/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch12alloc/mstats/</guid>
      <description>&lt;h1 id=&#34;128-内存统计&#34;&gt;12.8 内存统计&lt;/h1&gt;&#xA;&lt;p&gt;分配器一边干活，一边记账。每从操作系统批发一段地址空间、每切出一个 span、每分配或清扫&#xA;一个对象，运行时都会把对应的计数累加到一组全局变量里（&lt;code&gt;runtime.memstats&lt;/code&gt;）。这套账目不是&#xA;事后补记的统计报表，而是分配与回收过程中顺手写下的流水。它有两个去处：一是对外，让用户和&#xA;监控系统看见进程的内存形态；二是对内，&lt;a href=&#34;../../ch13gc/pacing&#34;&gt;GC 的调步器（13.3）&lt;/a&gt;与&#xA;&lt;a href=&#34;.././pagealloc&#34;&gt;软内存限制 &lt;code&gt;GOMEMLIMIT&lt;/code&gt;（12.7）&lt;/a&gt;都要读这套账来决定「下一次该在多大的堆上&#xA;触发回收」。换言之，记账闭合了一条反馈回路：分配产生数据，数据驱动决策，决策又约束分配。&lt;/p&gt;&#xA;&lt;p&gt;这一节先讲清楚账目里那几个最常被误读的字段，再讲读取它们的两套接口，最后说明这套数据如何&#xA;驱动 GC 的节奏，以及在 pprof 与监控里该怎么看。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1281-账目里有什么关键字段速写&#34;&gt;12.8.1 账目里有什么：关键字段速写&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;runtime.MemStats&lt;/code&gt; 是对外暴露的快照结构，字段近三十个。逐个翻译没有意义，真正要分清的是&#xA;两条平行的序列。下面是裁剪后的速写，只留与「读懂内存形态」相关的字段，并在注释里说明它&#xA;量的是什么：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// runtime.MemStats（速写）：两条平行序列 + GC 节奏&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;MemStats&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// [真实在用的内存：Alloc / Inuse 系列]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;HeapAlloc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 存活堆对象的字节数：已分配但 GC 尚未清扫的也算在内&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;HeapInuse&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 至少含一个对象的 span 占的字节（含尺寸类带来的内部碎片）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;HeapObjects&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 存活堆对象个数（= Mallocs - Frees）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// [向操作系统要来的地址空间：Sys 系列]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Sys&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 从 OS 取得的虚拟地址空间总量，是下面各 XSys 之和&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;HeapSys&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 为堆保留的地址空间（含已保留未提交的部分，见 12.3）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;HeapIdle&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 空闲 span 占的字节：无对象，可还给 OS 或留作复用&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;HeapReleased&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 已用 madvise 还给 OS 的物理内存（仍占地址空间）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// [GC 节奏]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;NextGC&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 下一轮 GC 的目标堆大小，目标是让 HeapAlloc ≤ NextGC&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;NumGC&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 已完成的 GC 轮数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PauseTotalNs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 程序启动至今 STW 暂停的累计纳秒&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PauseNs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;256&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 最近若干轮的 STW 暂停时长（环形缓冲）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;把字段分成「真实在用」与「地址空间」两摞，是读懂内存统计的全部要害。&lt;/p&gt;</description>
    </item>
    <item>
      <title>12.9 过去、现在与未来</title>
      <link>/zh-cn/part4memory/ch12alloc/history/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch12alloc/history/</guid>
      <description>&lt;h1 id=&#34;129-过去现在与未来&#34;&gt;12.9 过去、现在与未来&lt;/h1&gt;&#xA;&lt;p&gt;分配器不是一蹴而就的，它随 Go 的演化不断被打磨。回顾这条演进线，能看清当前设计是从哪些权衡里&#xA;长出来的，也能瞥见未来的方向。一个值得先记住的判断是：分配器的每一次大改动，几乎都不是为了&#xA;「分配本身更快」，而是为了在分配速度、内存占用、与垃圾回收的协同这三者之间重新落子。读懂这条&#xA;主线，下面几次重写就不再是孤立的版本琐事，而是同一道工程命题被反复求解的过程。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1291-过去从-tcmalloc-到-go-化&#34;&gt;12.9.1 过去：从 tcmalloc 到 Go 化&lt;/h2&gt;&#xA;&lt;p&gt;Go 分配器最初是 &lt;strong&gt;tcmalloc&lt;/strong&gt;（&lt;a href=&#34;.././basic&#34;&gt;12.1&lt;/a&gt;）的一个 Go 移植：继承了线程缓存、尺寸类、&#xA;中心仓库这套骨架（&lt;a href=&#34;.././component&#34;&gt;12.2&lt;/a&gt; 的 mcache / mcentral / mheap 正是它的三层对应）。&#xA;这套骨架解决的是同一个老问题：多线程下 &lt;code&gt;malloc&lt;/code&gt; 的锁争用。tcmalloc 的答案是把绝大多数分配挡在&#xA;每线程的本地缓存里，无锁完成，Go 原样继承了这个判断，只是把「每线程」换成了更贴合其调度模型的&#xA;「每 P」（&lt;a href=&#34;../../../part3concurrency/ch09sched/mpg&#34;&gt;9.3&lt;/a&gt;）。&lt;/p&gt;&#xA;&lt;p&gt;但 Go 分配器逐步「Go 化」，长出了 tcmalloc 没有、也不需要的东西。最根本的一处，是为&#xA;&lt;strong&gt;精确垃圾回收&lt;/strong&gt;服务的元数据。C 的 tcmalloc 服务于手动 &lt;code&gt;free&lt;/code&gt;：调用者明确告诉它「这块还给你」，&#xA;它无须知道对象内部哪里是指针、哪里是标量，一块内存在它眼里只是一段待复用的字节。Go 没有手动&#xA;&lt;code&gt;free&lt;/code&gt;，回收由 GC 接管，而精确 GC 要能从任意一个堆地址出发，判断「这里是不是一个指针、它指向&#xA;的对象是否存活」。这就要求分配器在交出每一块内存的同时，附带足够的类型与存活信息：每个 span 的&#xA;指针位图（哪些字是指针）、尺寸类里是否含指针的 &lt;code&gt;noscan&lt;/code&gt; 标记、以及与清扫共用的标记位&#xA;（&lt;a href=&#34;.././component&#34;&gt;12.2&lt;/a&gt; 里 mspan 的 &lt;code&gt;gcmarkBits&lt;/code&gt;、&lt;a href=&#34;../../ch13gc/sweep&#34;&gt;13.5&lt;/a&gt;）。&lt;/p&gt;&#xA;&lt;p&gt;这层元数据是 Go 分配器与其祖先最深的分野。它不是锦上添花，而是「与 GC 共生」这条设计原则&#xA;（&lt;a href=&#34;.././basic&#34;&gt;12.1&lt;/a&gt;）在数据结构上的兑现：分配器交出内存的那一刻，就已经为日后的扫描与回收&#xA;铺好了路。代价也实打实，每一块堆内存都要为这层信息付出额外的空间，而这层信息存在哪里、怎样&#xA;组织，恰恰成了后续多次重写的战场。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.1 垃圾回收的基本想法</title>
      <link>/zh-cn/part4memory/ch13gc/basic/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/basic/</guid>
      <description>&lt;h1 id=&#34;131-垃圾回收的基本想法&#34;&gt;13.1 垃圾回收的基本想法&lt;/h1&gt;&#xA;&lt;p&gt;垃圾回收（GC）把程序员从手动 &lt;code&gt;free&lt;/code&gt; 中解放出来，代价是运行时要自己判断「哪些内存还有用、&#xA;哪些可以收回」。Go 的 GC 以低延迟为首要目标，它宁可牺牲一点吞吐与内存，也要把卡顿&#xA;（stop-the-world 停顿）压到亚毫秒级。这一节建立 GC 的理论坐标：可达性、标记清扫、三色抽象，&#xA;以及 Go 在 GC 设计空间里的定位。后续各节是这套基本想法的展开。&lt;/p&gt;&#xA;&lt;p&gt;在展开之前，先借用一组贯穿全章的术语。Dijkstra 等人 [Dijkstra et al., 1978] 把一个带 GC 的&#xA;程序拆成两个半独立的角色：赋值器（mutator）与回收器（collector）。赋值器即用户态代码，&#xA;它只做一件与 GC 相关的事，修改对象之间的引用关系，也就是在一张「对象图」上增删有向边；&#xA;回收器则是运行时里负责找出并收回垃圾的那部分代码。本章的全部难处，都源自这两者要同时运行。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1311-可达性即存活&#34;&gt;13.1.1 可达性即存活&lt;/h2&gt;&#xA;&lt;p&gt;GC 判断存活的依据是可达性：从一组根（root）出发，凡能沿指针到达的对象都算存活，&#xA;到达不了的就是垃圾。Go 的根集合有三类，全局变量、各 goroutine 栈上的变量、以及寄存器中&#xA;可能存放的指针。回收器从这些根出发遍历对象图，标记一路可达的对象。&lt;/p&gt;&#xA;&lt;p&gt;可达性是一个保守而安全的近似。它并不真正知道某个对象将来还会不会被读到，只知道「从根够不到的，&#xA;程序一定再也用不上」。于是「这块内存是否还有用」这个语义问题，被转化成了一个图遍历问题：&#xA;从根开始走对象图，走得到的留下，走不到的回收。&lt;/p&gt;&#xA;&lt;p&gt;这里还藏着一个 Go 与许多 C/C++ 上的回收器的分野。Go 的 GC 是精确（precise，亦称 type-accurate）&#xA;的：运行时借助编译期生成的类型信息与 span 元数据（&lt;a href=&#34;../../ch12alloc/component&#34;&gt;12.2&lt;/a&gt;），&#xA;能准确分辨一个字（word）究竟是指针还是普通整数。保守式回收器做不到这一点，它只能把任何&#xA;「看起来像指针」的位模式都当作指针看待，因而可能误判、且无法移动对象。精确是 Go 后续许多&#xA;设计（写屏障、栈扫描）得以成立的前提。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1312-标记清扫&#34;&gt;13.1.2 标记—清扫&lt;/h2&gt;&#xA;&lt;p&gt;最经典的实现是标记—清扫（mark-sweep），由 McCarthy 在 1960 年实现 Lisp 时首次提出&#xA;[McCarthy, 1960]。它把回收分成两个阶段。标记阶段从根出发遍历对象图，给每个可达对象打上标记：&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.2 写屏障技术</title>
      <link>/zh-cn/part4memory/ch13gc/barrier/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/barrier/</guid>
      <description>&lt;h1 id=&#34;132-写屏障技术&#34;&gt;13.2 写屏障技术&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt; 给出了三色抽象与并发回收的轮廓：回收器把对象从白染灰、由灰染黑，&#xA;让一道「灰色波面」在对象图上单调推进，波面扫过之处即为已确认存活。若赋值器（mutator，&#xA;即用户态代码）在波面推进时停下不动，这套抽象自洽且会正确收敛。问题恰恰在于赋值器不会停。&#xA;并发回收的根本困难是：回收器追踪对象图的同时，赋值器在改写它，两者对同一张图各执一词。&lt;/p&gt;&#xA;&lt;p&gt;这一节要回答的是：赋值器的一次指针写入，凭什么不会让回收器漏标一个存活对象。答案是&lt;strong&gt;写屏障&lt;/strong&gt;&#xA;（write barrier）：编译器在堆指针写入处插入的一小段代码，让赋值器把「我动了哪条边」告知回收器。&#xA;我们先看清并发改图为何会漏标（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1321-%e5%b9%b6%e5%8f%91%e6%94%b9%e5%9b%be%e4%b8%ba%e4%bd%95%e4%bc%9a%e6%bc%8f%e6%a0%87&#34;&gt;13.2.1&lt;/a&gt;），由此引出两条三色不变性&#xA;（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1322-%e4%b8%a4%e6%9d%a1%e4%b8%89%e8%89%b2%e4%b8%8d%e5%8f%98%e6%80%a7&#34;&gt;13.2.2&lt;/a&gt;）；再看堵住漏标的两个屏障族（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1323-%e4%b8%a4%e4%b8%aa%e5%b1%8f%e9%9a%9c%e6%97%8f-dijkstra-%e6%8f%92%e5%85%a5%e4%b8%8e-yuasa-%e5%88%a0%e9%99%a4&#34;&gt;13.2.3&lt;/a&gt;）；&#xA;最后落到 Go 自 1.8 起沿用至今的混合写屏障（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1324-go-%e7%9a%84%e6%b7%b7%e5%90%88%e5%86%99%e5%b1%8f%e9%9a%9c&#34;&gt;13.2.4&lt;/a&gt;），&#xA;理解它为何能一举消除标记终止阶段那次代价高昂的栈重扫。&lt;/p&gt;&#xA;&lt;p&gt;需要先厘清一处常见的名词混淆。本节的「写屏障」是垃圾回收意义上的赋值器屏障，是一段&lt;strong&gt;软件&lt;/strong&gt;&#xA;逻辑，它与 CPU 层面阻止访存重排的内存屏障（memory barrier，见 &lt;a href=&#34;../../../part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;）&#xA;是两回事。两者唯一的交点在于：Go 的写屏障实现为保证自身在多核下的正确性，确实依赖了一处访存&#xA;顺序约束，这一点留到 &lt;a href=&#34;/zh-cn/part4memory/ch13gc/barrier/#1324-go-%e7%9a%84%e6%b7%b7%e5%90%88%e5%86%99%e5%b1%8f%e9%9a%9c&#34;&gt;13.2.4&lt;/a&gt; 再谈。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1321-并发改图为何会漏标&#34;&gt;13.2.1 并发改图为何会漏标&lt;/h2&gt;&#xA;&lt;p&gt;考虑回收器正扫到一半的某个瞬间。设灰色对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;A&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 指向白色对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，赋值器此时并发地做两件事：&#xA;把一个已被染黑的对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;C&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.07153em;&#34;&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 指向 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，再把 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;A&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 到 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的那条引用抹去。于是 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 现在只挂在黑色&#xA;对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;C&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.07153em;&#34;&gt;C&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 之下。回收器认为黑色对象已扫描完毕、不会再访问，而通往 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的灰色入口又被抹掉了，&#xA;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;B&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;B&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05017em;&#34;&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 就此从波面上消失，最终被当作垃圾错误回收。&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    subgraph before[&amp;#34;改图前&amp;#34;]&#xA;        A1[&amp;#34;A 灰&amp;#34;]:::grey --&amp;gt;|ref| B1[&amp;#34;B 白&amp;#34;]:::white&#xA;        C1[&amp;#34;C 黑&amp;#34;]:::black&#xA;    end&#xA;    subgraph after[&amp;#34;改图后：B 漏标&amp;#34;]&#xA;        A2[&amp;#34;A 灰&amp;#34;]:::grey -.-&amp;gt;|删除 ref| B2[&amp;#34;B 白&amp;#34;]:::white&#xA;        C2[&amp;#34;C 黑&amp;#34;]:::black --&amp;gt;|新增 ref| B2&#xA;    end&#xA;    before --&amp;gt; after&#xA;    classDef white fill:#fff,stroke:#333,color:#000&#xA;    classDef grey fill:#bbb,stroke:#333,color:#000&#xA;    classDef black fill:#333,stroke:#000,color:#fff&lt;/pre&gt;&#xA;&lt;p&gt;把这一瞬拆开看，漏标恰由两件事&lt;strong&gt;同时&lt;/strong&gt;发生才成立。[Wilson, 1992] 将其精确为两个条件：&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.3 触发频率及其调步算法</title>
      <link>/zh-cn/part4memory/ch13gc/pacing/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/pacing/</guid>
      <description>&lt;h1 id=&#34;133-触发频率及其调步算法&#34;&gt;13.3 触发频率及其调步算法&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt; 交代了写屏障如何让并发标记与赋值器（mutator）共处，保证「正在回收的同时&#xA;还能继续分配」。这就留下了一个时间问题：什么时候该启动下一轮 GC？太晚，堆已经涨到不可接受的&#xA;体积；太早，回收频繁地白白烧掉 CPU。回答这个问题的，是 Go 从 1.5 起引入、1.18 重新设计的&#xA;&lt;strong&gt;调步器&lt;/strong&gt;（pacer）。本节先把一轮 GC 的完整时序摆出来，再讲清调步器是一个什么样的反馈控制器：&#xA;它要在赋值器持续分配的过程中，赶在堆触到目标之前完成标记。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1331-一轮-gc-的时序&#34;&gt;13.3.1 一轮 GC 的时序&lt;/h2&gt;&#xA;&lt;p&gt;把一轮 GC 拆开看，它在 &lt;code&gt;_GCoff&lt;/code&gt;、&lt;code&gt;_GCmark&lt;/code&gt;、&lt;code&gt;_GCmarktermination&lt;/code&gt; 三个阶段间流转，其间夹着&#xA;两段极短的 STW（stop-the-world）：&lt;/p&gt;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;stateDiagram-v2&#xA;    [*] --&amp;gt; GCoff&#xA;    GCoff --&amp;gt; STW1: heap_live ≥ trigger&#xA;    note right of STW1&#xA;        极短 STW：开启写屏障&#xA;        扫描栈与全局根&#xA;    end note&#xA;    STW1 --&amp;gt; GCmark: 切入并发标记&#xA;    state GCmark {&#xA;        background: 后台标记 worker（约 25% CPU）&#xA;        assist: 赋值器 mark assist（分配快者被征用）&#xA;        background --&amp;gt; assist&#xA;        assist --&amp;gt; background&#xA;    }&#xA;    GCmark --&amp;gt; STW2: 标记队列排空&#xA;    note right of STW2&#xA;        极短 STW：标记终止&#xA;        关闭写屏障并结算&#xA;    end note&#xA;    STW2 --&amp;gt; Sweep: 转入并发清扫&#xA;    Sweep --&amp;gt; GCoff: 清扫随分配惰性推进&#xA;    GCoff --&amp;gt; [*]&lt;/pre&gt;&#xA;&lt;p&gt;整轮的脉络是这样：堆上的存活字节 &lt;code&gt;heap_live&lt;/code&gt; 涨到调步器算出的触发线 &lt;code&gt;trigger&lt;/code&gt;，第一段 STW&#xA;开启写屏障并扫描根（goroutine 栈与全局变量），随即放开世界进入并发标记。标记阶段，后台&#xA;worker 占用约 25% 的 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 持续标记，分配过快的 goroutine 还会被征去做 mark assist&#xA;（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/pacing/#1335-%e8%b5%8b%e5%80%bc%e5%99%a8%e5%8d%8f%e5%8a%a9-mark-assist&#34;&gt;13.3.5&lt;/a&gt;）。标记队列排空后进入第二段 STW 做标记终止，关闭&#xA;写屏障并结算本轮统计量，之后转入并发清扫，清扫工作随后续分配惰性摊销（&lt;a href=&#34;.././sweep&#34;&gt;13.5&lt;/a&gt;）。&#xA;两段 STW 都被压到亚毫秒量级，绝大部分回收工作与赋值器并发进行，这正是 Go 低延迟 GC 的形态。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.4 扫描标记与标记辅助</title>
      <link>/zh-cn/part4memory/ch13gc/mark/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/mark/</guid>
      <description>&lt;h1 id=&#34;134-扫描标记与标记辅助&#34;&gt;13.4 扫描标记与标记辅助&lt;/h1&gt;&#xA;&lt;p&gt;标记是 GC 的主体工作：从根（全局变量、各 goroutine 的栈、寄存器）出发，沿指针把所有可达对象&#xA;置黑（&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt; 的三色抽象）。一个朴素的实现会停下整个世界、单线程地走完对象图，这正是&#xA;早年 Go 不堪的停顿来源。go1.5 之后，标记被改造成两件事同时成立的形态：它&lt;strong&gt;与用户程序并发&lt;/strong&gt;地推进，&#xA;又能在用户分配过快时&lt;strong&gt;把代价分摊&lt;/strong&gt;到分配者头上。这一节回答三个问题：标记如何并行展开而不互相踩踏；&#xA;扫描一个对象时怎么知道它的哪些字是指针；以及当用户一边分配、标记一边追赶时，凭什么保证标记不会&#xA;被永远甩在后面。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1341-谁来标记后台-worker-与-25-的预算&#34;&gt;13.4.1 谁来标记：后台 worker 与 25% 的预算&lt;/h2&gt;&#xA;&lt;p&gt;标记的主力是一组后台 goroutine，&lt;code&gt;gcBgMarkWorker&lt;/code&gt;。运行时为每个 P 准备一个，但并不让它们全速&#xA;空转抢占 CPU，而是把整个标记阶段的 CPU 预算固定在 &lt;strong&gt;GOMAXPROCS 的 25%&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// runtime/mgcpacer.go（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;const&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcBackgroundUtilization&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.25&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 标记阶段后台 GC 的固定 CPU 占用目标&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;为什么是一个固定比例，而不是「有多少干多少」？这是调步器（&lt;a href=&#34;.././pacing&#34;&gt;13.3&lt;/a&gt;）的核心约定：把 GC&#xA;对吞吐的侵蚀钉在一个可预测的水平上，剩下的 75% 留给用户程序。25% 这个数字本身是工程折中，高了伤&#xA;吞吐，低了标记追不上分配（追不上的缺口再由下文的标记辅助补齐）。&lt;/p&gt;&#xA;&lt;p&gt;固定预算落到具体 worker 上，会按 &lt;code&gt;GOMAXPROCS × 0.25&lt;/code&gt; 算出需要几个「专职」worker，余数用一个「零工」&#xA;worker 凑齐，于是有了三种工作模式：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 三种后台标记 worker 模式（runtime）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcMarkWorkerDedicatedMode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 专职：整个标记阶段独占一个 P，不被抢占&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcMarkWorkerFractionalMode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 零工：补齐 25% 预算的小数部分，到点即让出&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcMarkWorkerIdleMode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 闲时：P 没有用户 goroutine 可跑时顺手标记&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;举例：&lt;code&gt;GOMAXPROCS = 8&lt;/code&gt; 时 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;8&lt;/mn&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;0.25&lt;/mn&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;8 \times 0.25 = 2&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7278em;vertical-align:-0.0833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;×&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0.25&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，于是有 2 个专职 worker 各占一个 P；若是&#xA;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;5&lt;/mn&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mn&gt;0.25&lt;/mn&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1.25&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;5 \times 0.25 = 1.25&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7278em;vertical-align:-0.0833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;×&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0.25&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1.25&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，则 1 个专职 worker 加一个补齐 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;0.25&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;0.25&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0.25&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的零工 worker。专职 worker 在标记期间不被调度&#xA;器抢占，零工 worker 跑到 &lt;code&gt;fractionalUtilizationGoal&lt;/code&gt; 这个比例就主动让出，闲时 worker 则只在某个 P&#xA;找不到用户活儿干时被 &lt;code&gt;findRunnable&lt;/code&gt;（&lt;a href=&#34;../../../part3concurrency/ch09sched/schedule&#34;&gt;9.4 调度循环&lt;/a&gt;）顺手唤起。三者&#xA;合起来既稳住了 25% 的均值，又不浪费空闲算力。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.5 清扫与位图</title>
      <link>/zh-cn/part4memory/ch13gc/sweep/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/sweep/</guid>
      <description>&lt;h1 id=&#34;135-清扫与位图&#34;&gt;13.5 清扫与位图&lt;/h1&gt;&#xA;&lt;p&gt;标记（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）一旦结束，仍为白色的对象就是不可达的垃圾。把它们占据的内存收回、&#xA;重新交给分配器，是回收的最后一步：&lt;strong&gt;清扫&lt;/strong&gt;（sweep）。如果照搬教科书里「遍历堆、逐个释放死对象」&#xA;的写法，清扫的开销会与死对象的数量成正比，在一个动辄数百万对象的堆上，这是一笔不小的账。Go 的&#xA;清扫绕开了这笔账，它有三处值得讲清楚的设计：清扫靠&lt;strong&gt;位图翻转&lt;/strong&gt;而非逐对象处理完成；它与用户程序&#xA;&lt;strong&gt;并发&lt;/strong&gt;进行，且&lt;strong&gt;惰性&lt;/strong&gt;地把开销摊到分配路径上；以及它&lt;strong&gt;不移动对象&lt;/strong&gt;，因而接受碎片、放弃整理。&#xA;这三点都不是偶然，它们各自对应一处明确的工程取舍，本节逐一拆开。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1351-清扫即位图翻转&#34;&gt;13.5.1 清扫即位图翻转&lt;/h2&gt;&#xA;&lt;p&gt;回收的高效，根子在分配器的数据结构上。第 &lt;a href=&#34;../../ch12alloc/component&#34;&gt;12.2&lt;/a&gt; 节讲过，每个 span&#xA;被切成同一尺寸类的等大槽位，并配两份位图：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;allocBits&lt;/code&gt;：哪些槽&lt;strong&gt;已分配&lt;/strong&gt;，分配路径据此找空槽。&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;gcmarkBits&lt;/code&gt;：本轮标记中哪些槽被置位，即&lt;strong&gt;存活&lt;/strong&gt;，由标记阶段写入。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;清扫一个 span，核心动作出奇地短，把标记位图&lt;strong&gt;直接当作&lt;/strong&gt;新的分配位图：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// mspan.sweep 的核心（src/runtime/mgcsweep.go，速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mspan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;sweep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;preserve&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ……此前已处理 finalizer / weak / profile 等 special 记录……&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nalloc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;uint16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;countAlloc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// gcmarkBits 中置位的槽数 = 存活数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;allocCount&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nalloc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;freeindex&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 分配游标归零，下次从头扫空槽&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 关键三行：标记位图翻身成为分配位图&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;allocBits&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcmarkBits&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 存活的继续「在用」，未标记的就此「空闲」&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcmarkBits&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;newMarkBits&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nelems&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 备一份清零的标记位图，留给下一轮 GC&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;refillAllocCache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 重建 allocCache 位扫描缓存（见 12.2）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ……根据存活数决定 span 的去向，见 13.5.3……&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;翻转之后，被标记的槽在新 &lt;code&gt;allocBits&lt;/code&gt; 里仍是 1，继续算「在用」；没被标记的槽在新位图里是 0，&#xA;就此变成「空闲」，下次分配直接覆写。死对象既不需要被找出来，也不需要被逐个清掉。源码注释把&#xA;这种状态说得很准：一个未标记又未被重新分配的槽，处境「就像挂在自由表上一样」（analogous to&#xA;being on a freelist）。这正是本节标题里「免清扫」（sweep-free）的含义,死对象无需任何显式&#xA;「清理」动作，它们的槽只是在位图翻转的一瞬间被重新解释为可分配。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.6 标记终止阶段</title>
      <link>/zh-cn/part4memory/ch13gc/termination/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/termination/</guid>
      <description>&lt;h1 id=&#34;136-标记终止阶段&#34;&gt;13.6 标记终止阶段&lt;/h1&gt;&#xA;&lt;p&gt;并发标记（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）有一个不平凡的收尾问题：如何&lt;strong&gt;判定标记已经完成&lt;/strong&gt;。在单线程的&#xA;停顿式回收里，这个问题是平凡的，标记线程把灰色队列扫空，标记就结束了。可在并发世界里，&#xA;「我的本地队列空了」从来不等于「全局没有灰色对象了」。当一个工作协程刚把自己的队列扫空，&#xA;另一个 P 上的 mutator 可能正巧执行了一次指针写入，写屏障（&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt;）随之染灰了&#xA;一个新对象，把它塞进了那个 P 的本地缓存。判定终止，本质上是要在一个没有全局锁、工作分散&#xA;在各 P 本地缓存里的系统中，确认一个&lt;strong&gt;全局性质&lt;/strong&gt;：灰色集合为空，且未来不会再产生灰色对象。&lt;/p&gt;&#xA;&lt;p&gt;本节回答三件事：终止检测算法如何在无全局协调的前提下断定标记完成（&lt;code&gt;gcMarkDone&lt;/code&gt;）；为此&#xA;仍需的那一次短暂 STW（&lt;code&gt;_GCmarktermination&lt;/code&gt;）做了什么、为何能做到很短；以及为什么一个周期&#xA;里始终保留着首尾两次短 STW，运行时为何选择把每次 STW 的&lt;strong&gt;工作量&lt;/strong&gt;压到与堆栈规模无关，&#xA;而不去追逐字面意义上的零停顿。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1361-并发下标记完成为何难判定&#34;&gt;13.6.1 并发下「标记完成」为何难判定&lt;/h2&gt;&#xA;&lt;p&gt;把标记工作想象成一池灰色对象，分散存放在每个 P 的本地 &lt;code&gt;gcWork&lt;/code&gt; 缓存与若干全局工作缓冲里。&#xA;工作协程不断从池中取出灰色对象、扫描、把新发现的指针染灰投回池中，直到池空。难点在于&#xA;「池空」这个判断本身是分布式的：没有哪个线程能在不停下整个世界的前提下，瞬间窥见所有 P&#xA;的本地缓存都为空。&lt;/p&gt;&#xA;&lt;p&gt;更棘手的是写屏障的存在。只要 mutator 还在运行、写屏障还开着，一次 &lt;code&gt;*p = q&lt;/code&gt; 的指针写入就&#xA;可能凭空产生一个新的灰色对象。于是终止条件不是静态的「此刻池空」，而是动态的「池空，&#xA;且没有任何在途的、尚未被观测到的工作能再次填满它」。运行时用两个计数刻画前半句：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 标记是否已完成的快速判据（速写，runtime/mgc.go）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;gcIsMarkDone&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// nproc 个标记工作者全部处于等待态，且全局已无可取的标记工作&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;work&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nwait&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;work&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nproc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;gcMarkWorkAvailable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;gcIsMarkDone&lt;/code&gt; 只是必要条件，不是充分条件。它看到的是「此刻」的快照，无法排除某个 P 的&#xA;本地缓存里还压着尚未刷新到全局、因而对其他人不可见的工作。要把必要条件升级为充分条件，&#xA;需要一道&lt;strong&gt;全局屏障&lt;/strong&gt;，强制每个 P 把本地缓存抖落出来、让所有人看见，再复查。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.7 安全点分析</title>
      <link>/zh-cn/part4memory/ch13gc/safe/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/safe/</guid>
      <description>&lt;h1 id=&#34;137-安全点分析&#34;&gt;13.7 安全点分析&lt;/h1&gt;&#xA;&lt;p&gt;标记阶段（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）要扫描一个 goroutine 的栈，找出栈上所有指向堆的指针，把它们当作&#xA;GC 根加入灰色队列。这件事看似直白，实则藏着一个不易察觉的前提：&lt;strong&gt;栈上的一个机器字，到底是不是&#xA;指针？&lt;/strong&gt; 一个 64 位的字，可能是一个堆地址，也可能是一个恰好落在堆地址区间里的整数、一段被半拆&#xA;出来的浮点数、或一个尚未写入的垃圾值。若把非指针误当指针，会平白吊住一块本该回收的内存；若把&#xA;指针漏判成整数，则会回收掉仍在使用的对象,后者是致命的内存破坏。&lt;/p&gt;&#xA;&lt;p&gt;要分辨这两者，运行时需要一份「这一刻，栈上哪些位置存着指针」的精确说明。这份说明并非时时存在：&#xA;寄存器与栈槽的指针 / 非指针归属，随着指令逐条执行不断变化。编译器只在某些&lt;strong&gt;程序点&lt;/strong&gt;上为它生成&#xA;快照，这些点就是&lt;strong&gt;安全点&lt;/strong&gt;（safe-point）。GC 要精确扫描栈，就必须先把目标 goroutine 停在一个安全&#xA;点上。安全点这个概念在调度的抢占一节（&lt;a href=&#34;../../../part3concurrency/ch09sched/preemption&#34;&gt;9.7&lt;/a&gt;）已从&#xA;「何时可以打断一个 goroutine」的角度系统讲过；本节从 GC 的角度补足它的另一半含义：&lt;strong&gt;安全点是栈&#xA;上指针信息精确可读的那些瞬间&lt;/strong&gt;。这两半其实是同一件事，本节最后会让它们合流。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1371-栈图编译期记下每个安全点的指针布局&#34;&gt;13.7.1 栈图：编译期记下每个安全点的指针布局&lt;/h2&gt;&#xA;&lt;p&gt;编译器为每个函数生成若干&lt;strong&gt;栈图&lt;/strong&gt;（stack map，又称指针位图 pointer map）。一份栈图是一个位向量&#xA;（&lt;code&gt;bitvector&lt;/code&gt;），第 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;i&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6595em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 位为 1 表示「栈帧中第 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;i&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6595em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 个字长的槽此刻存着一个指针」。同一个函数在不同&#xA;程序点上的栈布局不同，于是编译器为每个安全点各记一份栈图，并用一张 &lt;code&gt;PCDATA&lt;/code&gt; 表把「程序计数器&#xA;PC」映射到「该 PC 对应哪一份栈图」。运行时扫描某一帧时，拿该帧将要返回到的 PC 去查表，便得到&#xA;这一帧此刻的指针布局。&lt;/p&gt;&#xA;&lt;p&gt;运行时读取栈图的入口是 &lt;code&gt;getStackMap&lt;/code&gt;。它对一帧返回三样东西：局部变量的指针位图、参数的指针位图、&#xA;以及该帧内的&lt;strong&gt;栈对象&lt;/strong&gt;（stack object）记录:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// getStackMap：用帧的返回 PC 查出本帧的指针布局（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;frame&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stkframe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;getStackMap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;debug&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bitvector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;objs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stackObjectRecord&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;targetpc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;frame&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;continpc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 本帧将返回到的 PC&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;frame&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 回退到 CALL 指令处再查表：安全点记录的是「调用点」上的布局&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;targetpc&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pcdata&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;pcdatavalue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;abi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PCDATA_StackMapIndex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;targetpc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 局部变量位图：funcdata 里取出本函数的栈图表，按 pcdata 索引取第几份&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stkmap&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stackmap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;funcdata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;abi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FUNCDATA_LocalsPointerMaps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;stackmapdata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stkmap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pcdata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 参数位图：另一张表 FUNCDATA_ArgsPointerMaps，同样按 pcdata 索引&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;两个细节值得点出。其一，&lt;code&gt;targetpc--&lt;/code&gt;,查表用的不是返回地址本身，而是退一格落到 &lt;code&gt;CALL&lt;/code&gt; 指令上。&#xA;原因是被打断的帧总是停在「调用了某个下层函数、正等它返回」的位置，编译器恰是在调用点处记录了&#xA;栈布局，所以要对齐到调用点而非调用后的下一条指令。其二，指针信息分两类来源：&lt;code&gt;FUNCDATA_LocalsPointerMaps&lt;/code&gt;&#xA;管局部变量，&lt;code&gt;FUNCDATA_ArgsPointerMaps&lt;/code&gt; 管参数区，两者用同一个 &lt;code&gt;pcdata&lt;/code&gt; 索引各取一份位图。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.8 分代假设与代际回收</title>
      <link>/zh-cn/part4memory/ch13gc/generational/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/generational/</guid>
      <description>&lt;h1 id=&#34;138-分代假设与代际回收&#34;&gt;13.8 分代假设与代际回收&lt;/h1&gt;&#xA;&lt;p&gt;学过 JVM 或 .NET 的人常会问一个尖锐的问题：Go 为什么&lt;strong&gt;不做分代 GC&lt;/strong&gt;？分代是 Java、.NET 等&#xA;主流托管运行时 GC 的标配，被视为高效回收的关键，几乎成了「现代 GC 应当如此」的常识。Go 偏偏&#xA;没采用。这一节讲清分代的思想、它的威力，以及 Go 不走这条路的理由。这里的答案值得讲究：它不是&#xA;「Go 团队没想到」，而是 Go 团队&lt;strong&gt;真的实现过一版非移动的分代 GC，量过性能，最后放弃了&lt;/strong&gt;。这是&#xA;一处极能体现「没有放之四海皆准的设计」的取舍。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1381-分代假设与代际回收的机制&#34;&gt;13.8.1 分代假设与代际回收的机制&lt;/h2&gt;&#xA;&lt;p&gt;分代 GC 建立在一条反复被经验证实的观察上，称作&lt;strong&gt;分代假设&lt;/strong&gt;（generational hypothesis）：&#xA;&lt;strong&gt;绝大多数对象都「英年早逝」&lt;/strong&gt;，刚分配不久就不再被引用。它有强弱两个版本，工程上倚重的是弱版本，&#xA;即「年轻对象的死亡率远高于年老对象」。Lieberman 与 Hewitt（1983）、Ungar（1984）正是抓住这条&#xA;规律设计了最早的代际回收器。&lt;/p&gt;&#xA;&lt;p&gt;既然新对象大多速朽，就按「年龄」给对象分代：&lt;strong&gt;新生代&lt;/strong&gt;（young generation）装新分配的对象，&#xA;&lt;strong&gt;老年代&lt;/strong&gt;（old generation）装活过若干轮回收、被认为「站稳脚跟」的对象。回收分两档：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;minor GC&lt;/strong&gt;：只扫新生代。因为垃圾几乎都在这里，且新生代通常很小，扫一遍极快。多数回收都是&#xA;minor GC。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;major GC / full GC&lt;/strong&gt;：偶尔才全堆扫一次，处理那些熬进老年代后才死去的对象。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;把回收工作集中在「小而垃圾密集」的新生代上，是分代 GC 威力的来源。用一点记号来量化：设新生代&#xA;存活率为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_y&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7167em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;、老年代存活率为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;o&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_o&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5806em;vertical-align:-0.15em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;o&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.15em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，分代假设说 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;≪&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;o&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_y \ll s_o&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.8252em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;≪&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5806em;vertical-align:-0.15em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;o&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.15em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。一次 minor GC 的扫描成本正比于&#xA;新生代的&lt;strong&gt;存活&lt;/strong&gt;对象数（标记-复制类回收器只触碰活对象），约为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_y \cdot N_y&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7306em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;⋅&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.9694em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:-0.109em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;；而它回收掉的垃圾&#xA;约为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;(1 - s_y) \cdot N_y&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;−&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.0361em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;⋅&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.9694em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.10903em;&#34;&gt;N&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:-0.109em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;s_y&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7167em;vertical-align:-0.2861em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;msupsub&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.1514em;&#34;&gt;&lt;span style=&#34;top:-2.55em;margin-left:0em;margin-right:0.05em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:2.7em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34; style=&#34;margin-right:0.03588em;&#34;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.2861em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 越小，单位扫描成本换来的回收收益越高，这正是「频繁、廉价的&#xA;minor GC」之所以划算的全部道理。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.9 请求假设与事务制导回收</title>
      <link>/zh-cn/part4memory/ch13gc/roc/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/roc/</guid>
      <description>&lt;h1 id=&#34;139-请求假设与事务制导回收&#34;&gt;13.9 请求假设与事务制导回收&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././generational&#34;&gt;13.8&lt;/a&gt; 讲过 Go 没有采用分代假设。读者或许会接着问：Go 有没有试过别的&#xA;「对象寿命假设」？有。这一节讲的是 Go 团队认真做过、最终放弃的一个实验，&lt;strong&gt;事务制导回收&lt;/strong&gt;&#xA;（Request-Oriented Collector, ROC）。讲它，不是因为它进了 Go，而是因为一个被放弃的设计，&#xA;往往比成功的设计更能照出约束所在。ROC 像一道做错了的证明题：错处恰好标出了 Go GC 设计空间&#xA;里那条不能逾越的边界。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1391-请求假设&#34;&gt;13.9.1 请求假设&lt;/h2&gt;&#xA;&lt;p&gt;服务端程序有一条很强的结构特征：工作以&lt;strong&gt;请求&lt;/strong&gt;为单位。一个请求进来，分配一批对象处理它，&#xA;请求一结束，这批对象绝大多数就该死了。Rick Hudson 与 Austin Clements 在 2016 年把这条观察&#xA;写成了&lt;strong&gt;请求假设&lt;/strong&gt;（request hypothesis）：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;由一个请求创建的对象，倾向于在该请求被处理完成时一同死亡。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;它与分代假设（&lt;a href=&#34;.././generational&#34;&gt;13.8&lt;/a&gt;）说的不是一回事。分代假设讲的是「年轻对象易死」，&#xA;是一条关于&lt;strong&gt;年龄&lt;/strong&gt;的统计规律；请求假设讲的是「同一请求的对象同生共死」，是一条关于&lt;strong&gt;归属&lt;/strong&gt;&#xA;的结构规律。对服务端来说，后者更贴合现实，也更有力：它不只说某批对象会死，还指明了它们&#xA;&lt;strong&gt;何时&lt;/strong&gt;死、&lt;strong&gt;成批&lt;/strong&gt;死。云上的典型 goroutine 正是这种形状，收一条消息、反序列化、算一遍、&#xA;序列化、把结果丢回 channel 或 socket，然后退出：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 每请求一个 goroutine：分配的对象多数随 goroutine 退出而死&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;handle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;net&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Conn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;decode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 这些中间对象&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;compute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 几乎都只在本 goroutine 内活动&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Write&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;encode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 函数返回后即成垃圾&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;若能利用这条规律，回收就能从「全局扫描找垃圾」退化成「请求一结束，把它的那批对象整批还&#xA;回去」,既快，又不必停下整个程序做全局 GC。问题只剩一个：怎么知道哪些对象「只属于这个&#xA;请求」？&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.10 终结器</title>
      <link>/zh-cn/part4memory/ch13gc/finalizer/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/finalizer/</guid>
      <description>&lt;h1 id=&#34;1310-终结器&#34;&gt;13.10 终结器&lt;/h1&gt;&#xA;&lt;p&gt;垃圾回收的常规剧情到&lt;a href=&#34;.././sweep&#34;&gt;清扫（13.5）&lt;/a&gt;就结束了：标记找出存活对象，清扫把死对象的&#xA;槽位归还给分配器。但有一类对象在死之前还想说一句话。它包着一份非内存资源，一个文件描述符、&#xA;一段 mmap、一个 C 侧分配的句柄，当 Go 这边的包装对象不可达时，那份底层资源也该随之释放，&#xA;而垃圾回收只懂内存，并不知道描述符要 &lt;code&gt;close&lt;/code&gt;。终结器（finalizer）就是为这条缝隙而设的钩子：&#xA;为对象登记一个函数，等垃圾回收判定它不可达时，运行时不立即释放它，而是先调用这个函数。&lt;/p&gt;&#xA;&lt;p&gt;这是一个诱人却危险的机制。诱人在于它看起来像 C++ 的析构函数，能把「资源释放」自动挂到「对象&#xA;消亡」上；危险在于 Go 的对象消亡由垃圾回收决定，时机不可预测，于是一切「必须发生」的释放都&#xA;不能托付给它。本节先把 &lt;code&gt;runtime.SetFinalizer&lt;/code&gt; 的机制讲清楚，再逐条点出它的陷阱，最后介绍&#xA;Go 1.24 引入的 &lt;code&gt;runtime.AddCleanup&lt;/code&gt; 这个更安全的替代品，并给出一条简单的抉择原则。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13101-setfinalizer-的机制&#34;&gt;13.10.1 SetFinalizer 的机制&lt;/h2&gt;&#xA;&lt;p&gt;登记一个终结器只需一行：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;File&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fd&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;File&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;openSomeFd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runtime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;SetFinalizer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;File&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;syscall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;SetFinalizer(obj, fn)&lt;/code&gt; 要求 &lt;code&gt;obj&lt;/code&gt; 是一个指针，指向由 &lt;code&gt;new&lt;/code&gt;、复合字面量取址或局部变量取址&#xA;得到的、堆上分配的对象；&lt;code&gt;fn&lt;/code&gt; 是一个只接收一个参数（类型可由 &lt;code&gt;obj&lt;/code&gt; 赋值）的函数。运行时把&#xA;这对关系记在该对象所在 mspan 的一条 &lt;strong&gt;special&lt;/strong&gt; 记录里（&lt;a href=&#34;../../ch12alloc/component&#34;&gt;12.2&lt;/a&gt;&#xA;讲过 mspan 的元数据），而不是塞进对象本身。&lt;code&gt;SetFinalizer(obj, nil)&lt;/code&gt; 撤销登记。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.11 Green Tea：面向局部性的标记</title>
      <link>/zh-cn/part4memory/ch13gc/greentea/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/greentea/</guid>
      <description>&lt;h1 id=&#34;1311-green-tea面向局部性的标记&#34;&gt;13.11 Green Tea：面向局部性的标记&lt;/h1&gt;&#xA;&lt;p&gt;前面几节把标记讲成一件「正确且及时」的事：写屏障（&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt;）保证不漏标，标记辅助&#xA;（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）保证标记追得上分配。可这两件事都没碰标记的另一笔账：&lt;strong&gt;标记跑得快不快&lt;/strong&gt;。&#xA;到了多核、大堆的今天，标记的瓶颈往往既不在算法的正确性，也不在 CPU 的算力，而在&lt;strong&gt;内存墙&lt;/strong&gt;：&#xA;处理器大半时间不是在算，而是在等一笔笔缓存未命中的内存读取返回。Green Tea 正是冲着这笔账去的。&lt;/p&gt;&#xA;&lt;p&gt;Green Tea 是 go1.25 引入的新标记算法，go1.26 起&lt;strong&gt;默认开启&lt;/strong&gt;（基线实验特性，用&#xA;&lt;code&gt;GOEXPERIMENT=nogreenteagc&lt;/code&gt; 可以关掉回退到旧标记器），实现集中在 &lt;code&gt;runtime/mgcmark_greenteagc.go&lt;/code&gt;。&#xA;它不改变三色抽象，也不引入任何新的屏障，只重排了标记器&lt;strong&gt;遍历对象的顺序&lt;/strong&gt;，让访存从随机变回顺序。&#xA;这一节把它拆开来讲：它要解决的局部性问题、推迟扫描的核心想法、内联在 span 上的两套位图、span 的&#xA;认领与窃取，以及最终落到稠密 / 稀疏两条扫描路径上的实现。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13111-问题标记是一场缓存灾难&#34;&gt;13.11.1 问题：标记是一场缓存灾难&lt;/h2&gt;&#xA;&lt;p&gt;回到经典的三色标记（&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt;）：从灰色队列取一个对象，扫描它内部的每个指针，把指向的&#xA;白色对象染灰入队，反复直到队空。这是一次以对象为单位的&lt;strong&gt;图遍历&lt;/strong&gt;，而图的边由程序的引用关系决定，&#xA;与对象在堆上的物理位置毫不相干。于是标记器的内存访问近乎随机：扫完手上这个对象，下一个要碰的对象&#xA;可能落在几兆字节之外的另一个 span 上，它的指针位图元数据又在第三处。&lt;/p&gt;&#xA;&lt;p&gt;这种「追着指针满内存乱跑」的访问模式，对现代 CPU 几乎是最坏情况：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;缓存命中率低&lt;/strong&gt;。相邻两次访问落在同一条缓存行的概率很小，绝大多数指针解引用都要等一次主存读取，&#xA;延迟以上百个时钟周期计。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;预取器失效&lt;/strong&gt;。硬件预取依赖可预测的访问步长，而指针追逐没有步长可言，预取器根本猜不中下一个地址。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;元数据访问被摊不开&lt;/strong&gt;。每扫一个对象都要单独取一次它的指针位图、size class 等元数据，这些零散的&#xA;小读取本身又是一串缓存未命中。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;后果是标记吞吐&lt;strong&gt;难以随核数线性扩展&lt;/strong&gt;：加核只是让更多核一起空等内存。这正是 go1.5 把停顿压到亚毫秒&#xA;（&lt;a href=&#34;.././mark&#34;&gt;13.4&lt;/a&gt;）之后，标记阶段剩下的最大一块成本，也是 Green Tea 要啃的骨头。值得注意的是，&#xA;它瞄准的维度和被放弃的分代、ROC（&lt;a href=&#34;.././generational&#34;&gt;13.8&lt;/a&gt;、&lt;a href=&#34;.././roc&#34;&gt;13.9&lt;/a&gt;）不同：那两条路想靠&#xA;对象的&lt;strong&gt;时间&lt;/strong&gt;结构（年龄、请求生命周期）少扫一些对象，代价是长期开启的写屏障；Green Tea 想靠对象的&#xA;&lt;strong&gt;空间&lt;/strong&gt;结构（同一 span 上的对象物理相邻）把同样多的对象扫得更快，而不必动屏障。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13112-核心想法推迟扫描按-span-成批&#34;&gt;13.11.2 核心想法：推迟扫描，按 span 成批&lt;/h2&gt;&#xA;&lt;p&gt;Green Tea 的核心想法一句话能说清：&lt;strong&gt;推迟扫描，把指向同一个 span 的对象攒起来一次扫完&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;经典标记是「发现一个、立刻扫一个」。Green Tea 把这一步拆成两段：发现指向某个 span 的指针时，先只&#xA;在那个 span 上&lt;strong&gt;记一笔&lt;/strong&gt;（置标记位），并把这个 span 整体排进一个队列；真正的扫描推迟到稍后，等轮到&#xA;这个 span，再把它上面这段时间里攒下的、所有待扫对象&lt;strong&gt;一次性扫完&lt;/strong&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.12 过去、现在与未来</title>
      <link>/zh-cn/part4memory/ch13gc/history/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/history/</guid>
      <description>&lt;h1 id=&#34;1312-过去现在与未来&#34;&gt;13.12 过去、现在与未来&lt;/h1&gt;&#xA;&lt;p&gt;读完前面十节，读者手里已经握有 Go 垃圾回收器各个零件的现状：三色标记（&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt;）、&#xA;混合写屏障（&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt;）、调步器（&lt;a href=&#34;.././pacing&#34;&gt;13.3&lt;/a&gt;）、清扫与回收（&lt;a href=&#34;.././sweep&#34;&gt;13.5&lt;/a&gt;）。&#xA;这一节换一个角度，把这些零件放回时间轴上，看它们是怎样一步步长成今天这副模样的。&lt;/p&gt;&#xA;&lt;p&gt;回收器的演进史，读起来像一条始终朝同一个方向收敛的曲线。从 Go 1.0 到今天，停顿时间下降了约两个&#xA;数量级，而这一路上有一条不变的主线：每一步改动，都服务于「在不打扰用户代码的前提下完成回收」。&#xA;理解了这条主线，下面被采纳与被抛弃的诸多方案，就都能放到同一把尺子上衡量。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13121-被采纳的方案从数百毫秒到亚毫秒&#34;&gt;13.12.1 被采纳的方案：从数百毫秒到亚毫秒&lt;/h2&gt;&#xA;&lt;h3 id=&#34;go-1014朴素的停顿世界标记清扫&#34;&gt;Go 1.0–1.4：朴素的停顿世界标记清扫&lt;/h3&gt;&#xA;&lt;p&gt;最早的回收器是教科书式的标记清扫（mark-sweep），且整个过程停顿世界（stop-the-world，STW）：&#xA;一旦回收开始，所有用户 Goroutine 停下，回收器在单个或少数线程上走完「标记存活、清扫死亡」，&#xA;再放用户代码继续。Go 1.3 把清扫阶段改为与标记并行（parallel，多个回收线程同时干活），&#xA;缩短了总时长，但用户代码仍然是被整段挂起的。&lt;/p&gt;&#xA;&lt;p&gt;这套设计的代价直白地写在停顿时间上：堆越大，需要扫描的对象越多，STW 越长。实测停顿落在数十到&#xA;数百毫秒的量级。对一个正在处理请求的 Web 服务，这意味着尾延迟（tail latency）随时可能被一次回收&#xA;拖出一道百毫秒级的尖峰。这一痛点，定下了此后十年的主题。&lt;/p&gt;&#xA;&lt;h3 id=&#34;go-152015并发标记低延迟的转向&#34;&gt;Go 1.5（2015）：并发标记，低延迟的转向&lt;/h3&gt;&#xA;&lt;p&gt;Go 1.5 是分水岭。Richard Hudson 主导的这次重写，把标记阶段做成与用户代码&lt;strong&gt;并发&lt;/strong&gt;（concurrent）&#xA;执行：回收器一边标记，用户 Goroutine 一边运行、一边分配、一边改写指针。为了在指针被并发改写时&#xA;仍不漏标存活对象，引入了 Dijkstra 风格的写屏障（&lt;a href=&#34;.././barrier&#34;&gt;13.2&lt;/a&gt;）。这次重写公开喊出的目标，&#xA;是把 STW 压到 10 毫秒以下，并在博客与 ISMM 2018 的报告里反复强调一句立场：宁可牺牲一点吞吐与&#xA;峰值内存，也要换来可预测的低延迟。这是一次明确的价值排序，而非单纯的性能优化。&lt;/p&gt;&#xA;&lt;h3 id=&#34;go-1617把毫秒继续往下压&#34;&gt;Go 1.6、1.7：把毫秒继续往下压&lt;/h3&gt;&#xA;&lt;p&gt;并发标记落地后，剩下的 STW 集中在回收循环的起止两端。1.6 把回收器状态化、改进了标记终止&#xA;（mark termination）阶段的实现，1.7 让栈收缩（stack shrinking）独立于 STW 进行，停顿从 1.5 的&#xA;个位数毫秒进一步降到一两毫秒以内。这两个版本没有惊人的算法变动，做的是把 1.5 打下的并发框架&#xA;逐处打磨干净。&lt;/p&gt;</description>
    </item>
    <item>
      <title>13.13 垃圾回收统一理论</title>
      <link>/zh-cn/part4memory/ch13gc/unifiedgc/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch13gc/unifiedgc/</guid>
      <description>&lt;h1 id=&#34;1313-垃圾回收统一理论&#34;&gt;13.13 垃圾回收统一理论&lt;/h1&gt;&#xA;&lt;p&gt;本章一路看下来，Go 的并发标记清扫、混合写屏障、调步器，又对比了分代假设、请求制导回收等&#xA;别家的路子。算法五花八门，读者读到此处，心里或许积下一个疑问：这些机制之间，除了「都在做&#xA;垃圾回收」之外，有没有一条共同的脉络，把它们安放进同一个框架？&lt;/p&gt;&#xA;&lt;p&gt;有。Bacon、Cheng、Rajan 三人在 2004 年的论文《A Unified Theory of Garbage Collection》&#xA;里给出了一个出人意料的答案：人们习惯当作两大门派的&lt;strong&gt;追踪式&lt;/strong&gt;（tracing）与&lt;strong&gt;引用计数式&lt;/strong&gt;&#xA;（reference counting），在数学上是同一件事的两种写法，是&lt;strong&gt;对偶&lt;/strong&gt;的；而所有现实中的回收器，&#xA;都是这两端之间的某种混合。用这个理论收束全章，能让人从「记住了若干算法」上升到「看清了算法&#xA;空间的结构」,这正是一章理论收尾应当做的事。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13131-两个门派与它们各自的缺口&#34;&gt;13.13.1 两个门派与它们各自的缺口&lt;/h2&gt;&#xA;&lt;p&gt;先把两端摆清楚。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;追踪式&lt;/strong&gt;从根集合（栈、寄存器、全局变量）出发，沿指针逐步走遍对象图，凡能到达的都标记为&#xA;存活，走完后未被标记的即是垃圾。Go 用的就是这一类（&lt;a href=&#34;.././basic&#34;&gt;13.1&lt;/a&gt;）。它的好处是天然处理&#xA;循环引用：一个互相指向却无人从外部引用的对象环，从根出发根本到不了，自然被判死。它的代价是&#xA;回收要成批进行：得等一轮遍历走完，才知道谁是垃圾，于是有「停顿」这件事，也才有本章前面那一&#xA;整套为削减停顿而生的并发标记与写屏障机制。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;引用计数式&lt;/strong&gt;给每个对象配一个计数器，记录有多少指针指向它。每次指针赋值，给新指向的对象&#xA;加一、给旧指向的对象减一；计数归零，对象立即回收。它的好处是回收&lt;strong&gt;及时且分摊&lt;/strong&gt;：内存在最后&#xA;一个引用消失的瞬间就还回去，停顿被打散进每一次指针写入里。它的代价正好与追踪式相反：&lt;strong&gt;处理&#xA;不了循环引用&lt;/strong&gt;,环内对象彼此把对方的计数顶在一以上，谁也归不了零，于是泄漏。&lt;/p&gt;&#xA;&lt;p&gt;两个门派各有一个缺口，而且缺口的位置恰好互补。这种「恰好互补」不是巧合，它是下一节那条对偶&#xA;关系在工程表象上的投影。&lt;/p&gt;&#xA;&lt;h2 id=&#34;13132-追踪与计数是对偶&#34;&gt;13.13.2 追踪与计数是对偶&lt;/h2&gt;&#xA;&lt;p&gt;统一理论的核心，是用同一个不动点（fixed point）方程刻画两种算法，二者只是求同一个解的两个&#xA;方向。&lt;/p&gt;&#xA;&lt;p&gt;把内存里的对象看成一张有向图。设对象集合为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;V&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;V&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.22222em;&#34;&gt;V&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，指针构成的有向边的多重集为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;E&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05764em;&#34;&gt;E&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;（多重，&#xA;因为一个对象可以有多个字段指向同一目标）。我们保守地假定：凡从根可达的对象，将来都可能被用&#xA;到，因而不可回收。记根集合为 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;R&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;R&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.00773em;&#34;&gt;R&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;。对每个对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;∈&lt;/mo&gt;&lt;mi&gt;V&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;v \in V&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5782em;vertical-align:-0.0391em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;∈&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.22222em;&#34;&gt;V&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，定义它的引用计数 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\rho(v)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 为：来自&#xA;根的引用数，加上来自其他&lt;strong&gt;存活&lt;/strong&gt;对象的引用数。这是一个递归定义，写成不动点方程：&lt;/p&gt;&#xA;&lt;span class=&#34;katex-display&#34;&gt;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34; display=&#34;block&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mo fence=&#34;true&#34; stretchy=&#34;true&#34; minsize=&#34;1.2em&#34; maxsize=&#34;1.2em&#34;&gt;∣&lt;/mo&gt;&lt;mo stretchy=&#34;false&#34;&gt;[&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;:&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;∈&lt;/mo&gt;&lt;mi&gt;R&lt;/mi&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo stretchy=&#34;false&#34;&gt;]&lt;/mo&gt;&lt;mo fence=&#34;true&#34; stretchy=&#34;true&#34; minsize=&#34;1.2em&#34; maxsize=&#34;1.2em&#34;&gt;∣&lt;/mo&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mtext&gt;  &lt;/mtext&gt;&lt;mo fence=&#34;true&#34; stretchy=&#34;true&#34; minsize=&#34;1.2em&#34; maxsize=&#34;1.2em&#34;&gt;∣&lt;/mo&gt;&lt;mo stretchy=&#34;false&#34;&gt;[&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo separator=&#34;true&#34;&gt;,&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;:&lt;/mo&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo separator=&#34;true&#34;&gt;,&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;∈&lt;/mo&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo&gt;∧&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;&amp;gt;&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo stretchy=&#34;false&#34;&gt;]&lt;/mo&gt;&lt;mo fence=&#34;true&#34; stretchy=&#34;true&#34; minsize=&#34;1.2em&#34; maxsize=&#34;1.2em&#34;&gt;∣&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;&#xA;\rho(v) = \bigl|[\, v : v \in R \,]\bigr| \;+\; \bigl|[\,(w, v) : (w, v) \in E \,\wedge\, \rho(w) &amp;gt; 0 \,]\bigr|&#xA;&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.2em;vertical-align:-0.35em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;&lt;span class=&#34;delimsizing mult&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.85em;&#34;&gt;&lt;span style=&#34;top:-2.85em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.2em;&#34;&gt;&lt;/span&gt;&lt;span style=&#34;width:0.333em;height:1.200em;&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;0.333em&#34; height=&#34;1.200em&#34; viewBox=&#34;0 0 333 1200&#34;&gt;&lt;path d=&#34;M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15&#xA;c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15&#xA;c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.35em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.5782em;vertical-align:-0.0391em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;∈&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.2em;vertical-align:-0.35em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.00773em;&#34;&gt;R&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;&lt;span class=&#34;delimsizing mult&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.85em;&#34;&gt;&lt;span style=&#34;top:-2.85em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.2em;&#34;&gt;&lt;/span&gt;&lt;span style=&#34;width:0.333em;height:1.200em;&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;0.333em&#34; height=&#34;1.200em&#34; viewBox=&#34;0 0 333 1200&#34;&gt;&lt;path d=&#34;M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15&#xA;c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15&#xA;c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.35em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.2em;vertical-align:-0.35em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;&lt;span class=&#34;delimsizing mult&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.85em;&#34;&gt;&lt;span style=&#34;top:-2.85em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.2em;&#34;&gt;&lt;/span&gt;&lt;span style=&#34;width:0.333em;height:1.200em;&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;0.333em&#34; height=&#34;1.200em&#34; viewBox=&#34;0 0 333 1200&#34;&gt;&lt;path d=&#34;M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15&#xA;c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15&#xA;c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.35em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mpunct&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mpunct&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;∈&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.05764em;&#34;&gt;E&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;∧&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.2em;vertical-align:-0.35em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;&lt;span class=&#34;delimsizing mult&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.85em;&#34;&gt;&lt;span style=&#34;top:-2.85em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3.2em;&#34;&gt;&lt;/span&gt;&lt;span style=&#34;width:0.333em;height:1.200em;&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;0.333em&#34; height=&#34;1.200em&#34; viewBox=&#34;0 0 333 1200&#34;&gt;&lt;path d=&#34;M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15&#xA;c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15&#xA;c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.35em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;方括号取多重集，竖线取其基数。读法是：&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;v&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的计数，等于「根直接引用它的次数」，加上「所有&#xA;&lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;&amp;gt;&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\rho(w) &amp;gt; 0&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6444em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 的对象 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;w&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.4306em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 经由边 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;w&lt;/mi&gt;&lt;mo separator=&#34;true&#34;&gt;,&lt;/mo&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;(w, v)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1em;vertical-align:-0.25em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mopen&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.02691em;&#34;&gt;w&lt;/span&gt;&lt;span class=&#34;mpunct&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.1667em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03588em;&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;mclose&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; 引用它的次数」。注意右边又出现了 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;ρ&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\rho&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.625em;vertical-align:-0.1944em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34;&gt;ρ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，所以这是一个&#xA;递归方程，它的解是一个不动点。关键在于：&lt;strong&gt;这个方程有多个不动点，追踪式与引用计数式分别求出&#xA;其中一个。&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>14.1 连续栈的设计</title>
      <link>/zh-cn/part4memory/ch14stack/design/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch14stack/design/</guid>
      <description>&lt;h1 id=&#34;141-连续栈的设计&#34;&gt;14.1 连续栈的设计&lt;/h1&gt;&#xA;&lt;p&gt;每个 goroutine 都带着一个执行栈。它存放着函数调用的局部变量、参数、返回地址，是程序&#xA;得以「正在执行」的物理载体。读者熟悉的 C 程序里，这个栈由操作系统线程提供，大小在线程&#xA;创建时一次确定（&lt;code&gt;ulimit -s&lt;/code&gt; 默认常是 8MB），此后不再变化。Go 走了另一条路：goroutine 的栈&#xA;不是线程栈，而是运行时在堆上分配、自己管理、可以伸缩的一段内存，初始仅 2KB。&lt;/p&gt;&#xA;&lt;p&gt;这条设计选择不是细节，它是 goroutine 能够「便宜到可以开成千上万个」的前提&#xA;（&lt;a href=&#34;../../../part3concurrency/ch09sched/mpg&#34;&gt;9.3&lt;/a&gt;）。本节先把栈在运行时里的形态讲清楚：它由哪几个&#xA;字段描述、&lt;code&gt;stackguard0&lt;/code&gt; 如何在每个函数序言里守住栈底，再回到历史，看 Go 为什么从早年的&#xA;分段栈（segmented stack）切换到今天的连续栈（contiguous stack），以及这次切换换来了什么、&#xA;付出了什么。栈的分配、拷贝与收缩等机制留给本章后续各节，这里只谈设计。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1411-栈在运行时里的形态&#34;&gt;14.1.1 栈在运行时里的形态&lt;/h2&gt;&#xA;&lt;p&gt;一个 goroutine 由一个 &lt;code&gt;g&lt;/code&gt; 对象描述，它的头几个字段就刻画了执行栈。栈的边界用一对地址&#xA;表示，区间是半开的 &lt;code&gt;[lo, hi)&lt;/code&gt;，两端没有任何隐式的元数据：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// stack 描述一段栈内存，边界恰为 [lo, hi)，两侧无隐式结构（速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stack&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;lo&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 栈的低地址端（栈向低地址生长，lo 是「栈底极限」）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hi&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uintptr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 栈的高地址端（最初的栈顶）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;栈向低地址方向生长：函数调用压栈时，栈指针 SP 从 &lt;code&gt;hi&lt;/code&gt; 一侧朝 &lt;code&gt;lo&lt;/code&gt; 移动。&lt;code&gt;lo&lt;/code&gt; 因此是这段&#xA;栈所能用到的最低地址，越过它就是栈溢出。&lt;/p&gt;</description>
    </item>
    <item>
      <title>14.2 栈的分配与缓存</title>
      <link>/zh-cn/part4memory/ch14stack/alloc/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch14stack/alloc/</guid>
      <description>&lt;h1 id=&#34;142-栈的分配与缓存&#34;&gt;14.2 栈的分配与缓存&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././design&#34;&gt;14.1&lt;/a&gt; 把执行栈定位为一段 &lt;code&gt;[lo, hi)&lt;/code&gt; 的连续内存：它由运行时管理，本质上落在堆里，&#xA;所以每个 Goroutine 一启动就要先有人给它划出这段地址。问题随之而来：谁来划？划多大？划完之后&#xA;反复创建、销毁 Goroutine，这些两三 KB 的小块内存如果每次都直接找操作系统或全局堆去要，&#xA;锁与系统调用的代价会立刻压垮调度。&lt;/p&gt;&#xA;&lt;p&gt;答案是栈有它自己的一套分配器。它与 &lt;a href=&#34;/zh-cn/part4memory/ch12alloc/&#34;&gt;12 内存分配器&lt;/a&gt; 不是同一套代码，却几乎是&#xA;同一张设计图：&lt;strong&gt;每 P 无锁缓存在前，全局加锁仓库在后，堆与操作系统垫底&lt;/strong&gt;。读过 &lt;a href=&#34;../../ch12alloc/component&#34;&gt;12.2&lt;/a&gt;&#xA;的读者会发现，这一层不过是把 mcache / mcentral / mheap 的招式照搬到了栈上。本节先讲清这套&#xA;并行结构长什么样，再说清它为何要与对象分配分开、又如何与垃圾回收衔接。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1421-为什么栈要有自己的分配器&#34;&gt;14.2.1 为什么栈要有自己的分配器&lt;/h2&gt;&#xA;&lt;p&gt;把栈交给通用对象分配器，初看是省事的，可栈有三条与普通对象不同的性质，逼着运行时另起一套。&lt;/p&gt;&#xA;&lt;p&gt;其一，&lt;strong&gt;尺寸高度规整&lt;/strong&gt;。Go 的栈最小 2KB（&lt;code&gt;stackMin = 2048&lt;/code&gt;），每次伸缩都按 2 的幂翻倍&#xA;（&lt;a href=&#34;.././grow&#34;&gt;14.3&lt;/a&gt;），于是绝大多数栈只会是 2K / 4K / 8K / 16K 这几种大小。规整的尺寸天然适合&#xA;用自由表（free list，&lt;a href=&#34;../../ch12alloc/component&#34;&gt;12.2.1&lt;/a&gt;）按尺寸分桶管理，连尺寸类的查表都&#xA;省了，一次移位就能算出该去哪个桶。&lt;/p&gt;&#xA;&lt;p&gt;其二，&lt;strong&gt;生命周期与回收方式特殊&lt;/strong&gt;。栈不像普通对象那样被指针图引用、靠标记存活，它的存活与&#xA;对应的 Goroutine 绑定。栈内存被标成 &lt;code&gt;mSpanManual&lt;/code&gt; 状态（手动管理），不参与 GC 的标记清扫，&#xA;而是在栈收缩或 Goroutine 死亡时由运行时显式归还（&lt;a href=&#34;.././shrink&#34;&gt;14.5&lt;/a&gt;）。把这类「手动管理」的&#xA;内存与「自动回收」的对象内存分开放，回收逻辑才干净。&lt;/p&gt;&#xA;&lt;p&gt;其三，&lt;strong&gt;分配频率极高且在系统栈上&lt;/strong&gt;。&lt;code&gt;stackalloc&lt;/code&gt; 必须跑在调度栈（g0）上，因为它自己绝不能再&#xA;触发栈增长，否则会死锁（runtime issue 1547）。这条约束要求分配路径短、不可重入，更不能在&#xA;热路径上动辄加锁。&lt;/p&gt;&#xA;&lt;p&gt;这三条加起来，结论就是：给栈配一套尺寸固定、不走 GC、热路径无锁的专用分配器。它的形状，&#xA;和对象分配器是同构的。&lt;/p&gt;</description>
    </item>
    <item>
      <title>14.3 栈的增长</title>
      <link>/zh-cn/part4memory/ch14stack/grow/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch14stack/grow/</guid>
      <description>&lt;h1 id=&#34;143-栈的增长&#34;&gt;14.3 栈的增长&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././readme&#34;&gt;14.1&lt;/a&gt; 交代了 Go 选择连续栈的理由：栈起步只有 2KB，溢出时换一块两倍大的新栈，&#xA;把旧栈整个拷过去。这一节只回答其中一个问题：运行时是怎么在「正确的时刻」知道栈快满了，又是&#xA;怎么把控制权一路交到那段负责扩容的代码手里的。栈拷贝本身的细节留到 &lt;a href=&#34;.././copy&#34;&gt;14.4&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;p&gt;困难在于，栈增长这件事必须在栈快要溢出、却还没真正溢出的瞬间发生，而触发它的又恰恰是普通的&#xA;函数调用。换言之，每一次进入函数都要先问一句「我这一帧放得下吗」，放不下才去扩容。这道检查不能&#xA;靠程序员手写，它由编译器在每个函数的开头自动埋下，这就是&lt;strong&gt;函数序言&lt;/strong&gt;（prologue）里的分段检查。&#xA;整条链路是：序言检查 → &lt;code&gt;morestack&lt;/code&gt;（汇编）→ &lt;code&gt;newstack&lt;/code&gt;（Go）→ &lt;code&gt;copystack&lt;/code&gt; → 返回原处继续执行。&#xA;我们顺着这条链走一遍。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1431-函数序言每次调用都先问一句&#34;&gt;14.3.1 函数序言：每次调用都先问一句&lt;/h2&gt;&#xA;&lt;p&gt;每个 Goroutine 的 &lt;code&gt;g&lt;/code&gt; 结构里有一个字段 &lt;code&gt;stackguard0&lt;/code&gt;，它是「栈还能用到哪里」的警戒线&#xA;（&lt;a href=&#34;../../../part1overview/ch02asm/callconv&#34;&gt;2.2&lt;/a&gt;）。正常情况下它等于 &lt;code&gt;stack.lo + stackGuard&lt;/code&gt;，&#xA;即栈底再抬高一个保护区的位置。函数序言要做的，就是拿当前的栈指针 SP 和这条警戒线比较：栈向&#xA;低地址生长，一旦 SP 跌到 &lt;code&gt;stackguard0&lt;/code&gt; 之下，就说明剩余空间不足以容纳即将压入的这一帧，必须&#xA;先扩容。&lt;/p&gt;&#xA;&lt;p&gt;编译器并不会对所有函数都生成同样的检查。帧越大，越要把帧本身的大小算进去，于是检查分三档&#xA;（常量 &lt;code&gt;StackSmall = 128&lt;/code&gt;、&lt;code&gt;StackBig = 4096&lt;/code&gt; 见 &lt;code&gt;internal/abi/stack.go&lt;/code&gt;）。下面是 amd64 上&#xA;序言的伪汇编，对应 &lt;code&gt;cmd/internal/obj/x86/obj6.go&lt;/code&gt; 里的 &lt;code&gt;stacksplit&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-asm&#34; data-lang=&#34;asm&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 小帧 framesize &amp;lt;= StackSmall：直接比较 SP 与警戒线&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;nf&#34;&gt;CMPQ&lt;/span&gt;    &lt;span class=&#34;no&#34;&gt;SP&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;stackguard0&lt;/span&gt;     &lt;span class=&#34;c1&#34;&gt;// SP 是否已跌破警戒线？&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;nf&#34;&gt;JHI&lt;/span&gt;     &lt;span class=&#34;no&#34;&gt;ok&lt;/span&gt;                  &lt;span class=&#34;c1&#34;&gt;// SP 仍在警戒线之上，放得下，跳过&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;nf&#34;&gt;CALL&lt;/span&gt;    &lt;span class=&#34;no&#34;&gt;runtime&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;·&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;morestack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;SB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nl&#34;&gt;ok:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 中帧 StackSmall &amp;lt; framesize &amp;lt;= StackBig：把帧大小一并算进去&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;nf&#34;&gt;LEAQ&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;-(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;framesize-StackSmall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;SP&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;AX&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nf&#34;&gt;CMPQ&lt;/span&gt;    &lt;span class=&#34;no&#34;&gt;AX&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;stackguard0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nf&#34;&gt;JHI&lt;/span&gt;     &lt;span class=&#34;no&#34;&gt;ok&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nf&#34;&gt;CALL&lt;/span&gt;    &lt;span class=&#34;no&#34;&gt;runtime&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;·&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;morestack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;SB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 大帧 framesize &amp;gt; StackBig：还要防 SP 接近 0 时的回绕（wraparound），&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 并显式检查警戒线是否被改成了 stackPreempt 这种「故意大于任何真实 SP」的值&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;警戒线被读成内存操作数 &lt;code&gt;stackguard0&lt;/code&gt;，它在 &lt;code&gt;g&lt;/code&gt; 中的偏移恰是第 3 个字（前两个字是&#xA;&lt;code&gt;stack.lo&lt;/code&gt;、&lt;code&gt;stack.hi&lt;/code&gt;），所以汇编里写作 &lt;code&gt;2*PtrSize&lt;/code&gt; 处；C 函数则比对 &lt;code&gt;stackguard1&lt;/code&gt;&#xA;（第 4 个字）。被 &lt;code&gt;//go:nosplit&lt;/code&gt; 标记的函数不插入这段检查，代价是它们的帧必须挤进栈底那块&#xA;&lt;code&gt;stackGuard - StackSmall&lt;/code&gt; 的保留区里，链接器会遍历所有不分段函数的调用链，确保这块预留够用。&lt;/p&gt;</description>
    </item>
    <item>
      <title>14.4 栈的拷贝与指针调整</title>
      <link>/zh-cn/part4memory/ch14stack/copy/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch14stack/copy/</guid>
      <description>&lt;h1 id=&#34;144-栈的拷贝与指针调整&#34;&gt;14.4 栈的拷贝与指针调整&lt;/h1&gt;&#xA;&lt;p&gt;连续栈的代价集中在一处：当一个 goroutine 的栈溢出，运行时分配一段两倍大的新栈，把旧栈整个&#xA;搬过去，再释放旧栈。搬家这一步看似就是一次 &lt;code&gt;memmove&lt;/code&gt;，把 &lt;code&gt;[old.lo, old.hi)&lt;/code&gt; 的字节复制到&#xA;&lt;code&gt;[new.lo, new.hi)&lt;/code&gt;。倘若真的只是复制字节，问题就来了：栈上的变量里可能存着指向这个栈自身的&#xA;指针，比如某个局部变量取了另一个局部变量的地址。字节被原样搬到新地址后，这些指针的值没有变，&#xA;仍然指着旧栈的位置，而旧栈马上就要被回收。复制完成的那一刻，它们就成了悬垂指针。&lt;/p&gt;&#xA;&lt;p&gt;所以栈的拷贝不是 &lt;code&gt;memmove&lt;/code&gt;，而是 &lt;code&gt;memmove&lt;/code&gt; 加上一遍&lt;strong&gt;指针调整&lt;/strong&gt;：搬完字节之后，运行时必须&#xA;遍历新栈，把每一个原本指向旧栈区间的指针，统统加上一个固定的位移 &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-mathml&#34;&gt;&lt;math xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;δ&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;n&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;e&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;w&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;.&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;h&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;i&lt;/mi&gt;&lt;/mrow&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mrow&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;o&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;l&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;d&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;.&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;h&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;i&lt;/mi&gt;&lt;/mrow&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\delta = \mathrm{new.hi} -&#xA;\mathrm{old.hi}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6944em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord mathnormal&#34; style=&#34;margin-right:0.03785em;&#34;&gt;δ&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mrel&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2778em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.7778em;vertical-align:-0.0833em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathrm&#34;&gt;new.hi&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mbin&#34;&gt;−&lt;/span&gt;&lt;span class=&#34;mspace&#34; style=&#34;margin-right:0.2222em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:0.6944em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mord mathrm&#34;&gt;old.hi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;，让它改指向新栈的对应位置。难点不在「怎么加」，而在「怎么知道哪些字是指针」。&#xA;栈上的字没有类型标签，一个 8 字节的字到底是指针还是恰好长得像地址的整数，运行时无从分辨。&#xA;答案来自编译器：它为每个函数在每个安全点（&lt;a href=&#34;../../ch13gc/safe&#34;&gt;13.7&lt;/a&gt;）生成了&lt;strong&gt;栈映射&lt;/strong&gt;&#xA;（stack map），用位图精确记录该函数栈帧里哪些槽位是指针。GC 扫描栈靠它，栈拷贝调整指针也靠它。&lt;/p&gt;&#xA;&lt;p&gt;这一节就讲这件事：&lt;code&gt;copystack&lt;/code&gt; 如何在搬家之后，借栈映射走遍新栈，把所有指向旧栈的指针、加上&#xA;那些不在栈帧里、却同样指向栈的运行时结构（&lt;code&gt;gobuf&lt;/code&gt;、&lt;code&gt;sudog&lt;/code&gt;、&lt;code&gt;defer&lt;/code&gt;、&lt;code&gt;panic&lt;/code&gt;）一并调整正确。&#xA;读完会看到，正是「栈可以移动」这条性质，反过来约束了语言：哪些指针允许指向栈、逃逸分析&#xA;（&lt;a href=&#34;../../../part5toolchain/ch15compile/escape&#34;&gt;15.5&lt;/a&gt;）为何必须把外部持有的地址搬到堆上。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1441-为什么不能只-memmove&#34;&gt;14.4.1 为什么不能只 memmove&lt;/h2&gt;&#xA;&lt;p&gt;先把问题摆清楚。考虑一段普通的 Go 代码：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// p 指向本栈帧里的 x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 把栈上地址传下去&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;p&lt;/code&gt; 的值是 &lt;code&gt;x&lt;/code&gt; 在栈上的地址，落在 &lt;code&gt;[old.lo, old.hi)&lt;/code&gt; 区间内。当 &lt;code&gt;g&lt;/code&gt; 或其更深的调用触发栈增长，&#xA;整个栈被搬到新地址，&lt;code&gt;x&lt;/code&gt; 随之搬走，但 &lt;code&gt;p&lt;/code&gt; 这个字里存的还是旧地址。若不修正，&lt;code&gt;p&lt;/code&gt; 指向的已是&#xA;一段即将释放、或将被别的 goroutine 栈复用的内存。&lt;/p&gt;</description>
    </item>
    <item>
      <title>14.5 栈的收缩与演进</title>
      <link>/zh-cn/part4memory/ch14stack/shrink/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part4memory/ch14stack/shrink/</guid>
      <description>&lt;h1 id=&#34;145-栈的收缩与演进&#34;&gt;14.5 栈的收缩与演进&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././copy&#34;&gt;14.4&lt;/a&gt; 讲的是栈的「长大」：当函数序言（prologue）发现栈快要用尽，运行时把整段栈&#xA;拷贝到一块更大的内存上，栈指针随之平移。这是一条由执行本身驱动的单向力，它只会让栈越来越大。&#xA;可是一个 goroutine 的栈深并不单调。一段递归走到很深，再层层返回到浅处，它真正需要的栈早已&#xA;缩回，但那块为深递归而扩出的大栈仍挂在它名下。若只有增长没有归还，每个 goroutine 的栈最终都&#xA;会停在它历史上的最深点。对一个动辄持有百万 goroutine 的程序，这笔账无法承受。&lt;/p&gt;&#xA;&lt;p&gt;所以栈也会「缩小」。增长由序言触发，发生在执行的热路径上；收缩则交给垃圾回收，在扫描栈的间隙&#xA;顺手完成。两股力一推一收，让每个 goroutine 的栈始终贴着它当下的真实需要。这一节先看收缩的&#xA;时机与判据，再说清这套「自我调节」为何是轻量并发的地基，最后把它放回语言演进与跨系统的坐标里。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1451-收缩的时机与判据&#34;&gt;14.5.1 收缩的时机与判据&lt;/h2&gt;&#xA;&lt;p&gt;收缩不是随时能做的。拷贝栈要改写栈上所有指向栈内的指针（&lt;a href=&#34;.././copy&#34;&gt;14.4&lt;/a&gt;），这要求运行时&#xA;此刻对该 goroutine 的栈拥有精确的指针信息，并且没有别的执行流正踩在这段栈上。能同时满足这两点&#xA;的最自然的窗口，正是垃圾回收扫描栈的时候：GC 为了标记存活对象，本就要停住每个 goroutine、&#xA;逐帧扫描它的栈（&lt;a href=&#34;../../ch13gc/mark&#34;&gt;13.4&lt;/a&gt;、&lt;a href=&#34;../../ch13gc/mark&#34;&gt;13.7&lt;/a&gt;）。栈既然已经停稳、&#xA;指针图也已备齐，扫描完顺手判断要不要收缩，几乎是零额外成本。&lt;/p&gt;&#xA;&lt;p&gt;于是 &lt;code&gt;shrinkstack&lt;/code&gt; 由 &lt;code&gt;scanstack&lt;/code&gt; 调用。扫描在确认栈安全可缩后才动手；若当下不安全，就把意图&#xA;记在 &lt;code&gt;gp.preemptShrink&lt;/code&gt; 上，留到下一个同步安全点再补做：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// scanstack：GC 扫描某个 goroutine 的栈（节选自 runtime/mgcmark.go）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;scanstack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;g&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcw&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gcWork&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ... 统计本次要扫描的栈大小，用于初始栈尺寸的自适应估计 ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;isShrinkStackSafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;shrinkstack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 不安全的窗口之外，顺手收缩&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;preemptShrink&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 否则推迟到下一个同步安全点&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ... 继续扫描栈帧，标记其中的指针 ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;什么叫「安全」？&lt;code&gt;isShrinkStackSafe&lt;/code&gt; 把几类不能动栈的处境一一排除：goroutine 正陷在系统调用里&#xA;（&lt;code&gt;syscallsp != 0&lt;/code&gt;，系统调用可能持有指向栈的裸指针，且最内层栈帧没有精确指针图）；停在异步&#xA;抢占的安全点上（同样缺精确指针图）；正处在「已调用 &lt;code&gt;gopark&lt;/code&gt; 准备挂到 channel、但 &lt;code&gt;activeStackChans&lt;/code&gt;&#xA;尚未置位」的窗口；以及为了配合 &lt;code&gt;suspendG&lt;/code&gt; 而停在 &lt;code&gt;_Gwaiting&lt;/code&gt; 的 goroutine。任何一条命中，本轮&#xA;就不缩。&lt;/p&gt;</description>
    </item>
    <item>
      <title>15.1 词法与文法</title>
      <link>/zh-cn/part5toolchain/ch15compile/parse/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch15compile/parse/</guid>
      <description>&lt;h1 id=&#34;151-词法与文法&#34;&gt;15.1 词法与文法&lt;/h1&gt;&#xA;&lt;p&gt;编译的第一站，是把源码文本变成结构化的&lt;strong&gt;抽象语法树&lt;/strong&gt;（AST）。这要经过&lt;strong&gt;词法分析&lt;/strong&gt;（把字符流&#xA;切成 token）与&lt;strong&gt;语法分析&lt;/strong&gt;（按文法把 token 组织成树）。&lt;a href=&#34;../../../part1overview/ch03life/compile&#34;&gt;3.2&lt;/a&gt;&#xA;鸟瞰过整条流水线，这一节专看它的前端，以及 Go 的文法为何被设计得如此「好解析」。&lt;/p&gt;&#xA;&lt;p&gt;承担这两步的，是编译器里一个自成一体的包 &lt;code&gt;cmd/compile/internal/syntax&lt;/code&gt;。它由两件器物构成：&#xA;&lt;strong&gt;scanner&lt;/strong&gt;（词法器）按字符读入、吐出 token 流；&lt;strong&gt;parser&lt;/strong&gt;（语法器）以&lt;strong&gt;递归下降&lt;/strong&gt;的方式消费&#xA;token、建出语法树。这个包的注释甚至自豪地写明，它的几个文件 &lt;code&gt;scanner.go&lt;/code&gt;、&lt;code&gt;source.go&lt;/code&gt;、&#xA;&lt;code&gt;tokens.go&lt;/code&gt; 不依赖编译器其余部分，可单独编译成一个独立的库。词法与文法之所以能如此干净地切出来，&#xA;根子在 Go 文法本身的简单。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1511-为快速解析而设计的文法&#34;&gt;15.1.1 为快速解析而设计的文法&lt;/h2&gt;&#xA;&lt;p&gt;Go 的文法是&lt;strong&gt;刻意为快速解析而设计&lt;/strong&gt;的（&lt;a href=&#34;../../../part1overview/ch01intro/history&#34;&gt;1.1&lt;/a&gt; 的编译速度&#xA;执念）。关键的一点是：它&lt;strong&gt;正则到足以被 LALR(1) 解析&lt;/strong&gt;，因而无需复杂的回溯。早年的 gc 编译器正是&#xA;用 yacc 喂一份 LALR(1) 文法（&lt;code&gt;go.y&lt;/code&gt;）来解析 Go 的，这份文法的存在本身，就是「Go 文法可被&#xA;单遍、确定地解析」的证据。换言之，解析一段 Go 代码，编译器读一遍 token 流、只看眼前一个 token&#xA;就能决定怎么走，不必回头重试，也不必在解析途中查询符号表。&lt;/p&gt;&#xA;&lt;p&gt;这与 C/C++ 形成鲜明对比。C 的文法里，&lt;code&gt;a * b;&lt;/code&gt; 究竟是「&lt;code&gt;a&lt;/code&gt; 乘 &lt;code&gt;b&lt;/code&gt;」还是「声明一个指向 &lt;code&gt;a&lt;/code&gt;&#xA;类型的指针 &lt;code&gt;b&lt;/code&gt;」，取决于 &lt;code&gt;a&lt;/code&gt; 此刻是不是一个类型名，而这要查符号表才知道。解析与语义于是纠缠在&#xA;一起，C++ 更因此背上了著名的 &lt;em&gt;most vexing parse&lt;/em&gt;：&lt;code&gt;Widget w(Thing());&lt;/code&gt; 会被解析成函数声明而非&#xA;对象构造。Go 的文法刻意回避了这类歧义，任何一段 token 的结构都由文法唯一确定，与名字的含义无关。&#xA;解析器因此又快又简单，也不必把类型信息回灌给词法器。&lt;/p&gt;</description>
    </item>
    <item>
      <title>15.2 中间表示</title>
      <link>/zh-cn/part5toolchain/ch15compile/ssa/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch15compile/ssa/</guid>
      <description>&lt;h1 id=&#34;152-中间表示&#34;&gt;15.2 中间表示&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././parse&#34;&gt;15.1&lt;/a&gt; 把源码变成了语法树（AST）。AST 忠实记录了程序员写下的结构：变量、作用域、&#xA;表达式的嵌套。可一旦要做优化，这套结构就显得太「高」了。考虑一句最不起眼的 &lt;code&gt;x = x + 1&lt;/code&gt;：&#xA;在 AST 里，左边的 &lt;code&gt;x&lt;/code&gt; 与右边的 &lt;code&gt;x&lt;/code&gt; 是同一个名字，编译器若想知道「这次用到的 &lt;code&gt;x&lt;/code&gt; 究竟是哪一次&#xA;赋值产生的值」，就得反复做作用域查找与数据流分析。名字会被覆盖，作用域会嵌套，赋值会把旧值&#xA;冲掉，这些都让「一个值从哪来、到哪去」这件本该最基本的事变得晦暗。优化器最想要的，恰恰是把&#xA;数据流摊在明面上。&lt;/p&gt;&#xA;&lt;p&gt;于是 Go 编译器在 AST 与机器码之间，插入了一层专门为优化而生的&lt;strong&gt;中间表示&lt;/strong&gt;（intermediate&#xA;representation, IR）。它先把 AST 下降（lower）为一种更接近指令、却仍与具体机器无关的形式，&#xA;再转换成本节的主角：&lt;strong&gt;静态单赋值形式&lt;/strong&gt;（static single assignment, SSA）。SSA 是当代优化编译器&#xA;近乎统一的中段表示，LLVM、HotSpot 的 C2、GCC 的 GIMPLE-SSA 都建在它之上。本节回答三个问题：&#xA;SSA 是什么，为什么它让优化变得直接，以及 Go 的 SSA 流水线如何把一个函数从「与机器无关」一路&#xA;打磨到「为某个架构生成的机器码」。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1521-静态单赋值每个变量只赋值一次&#34;&gt;15.2.1 静态单赋值：每个变量只赋值一次&lt;/h2&gt;&#xA;&lt;p&gt;SSA 的定义只有一句话：&lt;strong&gt;程序中每个变量恰好被赋值一次&lt;/strong&gt;。一旦某个名字被多次赋值，就给它编号，&#xA;拆成多个互不相同的版本。回到 &lt;code&gt;x = 1; x = x + 1&lt;/code&gt;，在 SSA 里它变成：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;x_1 = 1&#xA;x_2 = x_1 + 1&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;两次对 &lt;code&gt;x&lt;/code&gt; 的赋值成了 &lt;code&gt;x_1&lt;/code&gt; 与 &lt;code&gt;x_2&lt;/code&gt; 两个独立的、各自只被赋值一次的值。这一步看似只是机械改名，&#xA;带来的好处却是结构性的：当后面某处用到 &lt;code&gt;x_2&lt;/code&gt;，它的来源是&lt;strong&gt;唯一且显式&lt;/strong&gt;的，就是上面那条&#xA;&lt;code&gt;x_1 + 1&lt;/code&gt;，无需任何作用域查找或数据流推断。「定义」与「使用」之间于是连成一张明确的图（&#xA;use-def chain），值从哪来、被谁用，一望可知。常量传播、公共子表达式消除、死代码删除这些优化，&#xA;本质上都是在这张图上做的局部改写，SSA 把它们从「需要全局分析」降格成了「照着图改」。&lt;/p&gt;</description>
    </item>
    <item>
      <title>15.3 优化器</title>
      <link>/zh-cn/part5toolchain/ch15compile/optimize/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch15compile/optimize/</guid>
      <description>&lt;h1 id=&#34;153-优化器&#34;&gt;15.3 优化器&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././ssa&#34;&gt;15.2&lt;/a&gt; 把前端的语法树降到了 SSA 这层中间表示，并说明了 SSA 的「每个变量只赋值一次」&#xA;为何让优化遍写起来又准又快。这一节接着往下走：在这层表示上，编译器到底跑了哪些优化，又为什么&#xA;偏偏是这几样。&lt;/p&gt;&#xA;&lt;p&gt;读懂 Go 优化器，先要读懂它的取向。同样是把高级语言变成机器码，GCC 与 LLVM 愿意花上几秒甚至几十秒，&#xA;把一个函数反复揉搓，换取最后那几个百分点的运行性能。Go 走的是另一条路：它只做性价比最高的那一批&#xA;优化，把省下来的时间还给编译速度（&lt;a href=&#34;../../../part1overview/ch01intro/history&#34;&gt;1.1&lt;/a&gt;）。这不是能力&#xA;不足，而是一道清醒的价值排序，本节最后会回到这条红线。我们先看 Go 愿意做的优化，再看 Go 1.21&#xA;引入的、把优化从「静态猜测」推向「数据驱动」的性能制导优化（PGO）。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1531-内联一切优化的入口&#34;&gt;15.3.1 内联：一切优化的入口&lt;/h2&gt;&#xA;&lt;p&gt;在 SSA 上做的诸多优化里，&lt;strong&gt;内联&lt;/strong&gt;（inlining）地位特殊。它本身只做一件朴素的事：把一个小函数的&#xA;函数体，直接展开到调用它的地方，省去一次函数调用的开销（建栈帧、传参、跳转、返回）。但它真正的&#xA;价值不在省这点开销，而在于它&lt;strong&gt;为其他优化创造了条件&lt;/strong&gt;。一次跨函数的调用，对优化器来说是一堵墙：&#xA;墙那边的代码长什么样、参数是不是常量、返回值会不会被用到，它一概不知。内联把这堵墙拆掉，调用点&#xA;两侧的代码合到一起，常量就能传播过去，分支就能被判定，死代码就能被消除。&lt;/p&gt;&#xA;&lt;p&gt;举一个最常见的例子。&lt;code&gt;sync.Once.Do&lt;/code&gt; 的快路径只是一次原子读，标准库把它写成一个独立的小方法：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;o&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Once&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Do&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;atomic&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;LoadUint32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;o&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;done&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;o&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;doSlow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;如果不内联，每次 &lt;code&gt;once.Do(f)&lt;/code&gt; 都要付一次调用开销，仅仅为了读一个字段。内联之后，&lt;code&gt;atomic.LoadUint32&lt;/code&gt;&#xA;这一层包装也一并展开，整个快路径塌缩成几条指令，与手写一个内联的原子读没有区别。Go 标准库里大量&#xA;「一层薄包装」的设计，正是建立在「编译器会把它内联掉」这个假设之上。想知道某个调用到底有没有被&#xA;内联、为什么没有，可以用 &lt;code&gt;go build -gcflags=-m&lt;/code&gt;，编译器会逐条打印它的内联决策（&lt;code&gt;can inline&lt;/code&gt;、&#xA;&lt;code&gt;inlining call to&lt;/code&gt;，或是 &lt;code&gt;function too complex&lt;/code&gt; 这类拒绝理由）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>15.4 指针检查器</title>
      <link>/zh-cn/part5toolchain/ch15compile/unsafe/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch15compile/unsafe/</guid>
      <description>&lt;h1 id=&#34;154-指针检查器&#34;&gt;15.4 指针检查器&lt;/h1&gt;&#xA;&lt;p&gt;Go 是一门内存安全的语言。在常规代码里，类型系统保证每个指针都指向它声明类型的合法对象，&#xA;垃圾回收（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/&#34;&gt;13&lt;/a&gt;）保证对象在仍被引用时不会被回收，运行时保证越界访问&#xA;被挡在边界检查里。这套保证不是免费的，它建立在「编译器始终知道每个值的类型与布局」之上。&#xA;可总有少数场景需要跳出这套体系：与 C 互操作（&lt;a href=&#34;.././cgo&#34;&gt;15.6&lt;/a&gt;）要按 C 的内存布局解释一段字节，&#xA;对接操作系统的系统结构体要逐字节摆放，零拷贝地把 &lt;code&gt;[]byte&lt;/code&gt; 重解释成 &lt;code&gt;string&lt;/code&gt;&#xA;（&lt;a href=&#34;../../../part2lang/ch05data/slice&#34;&gt;5.1&lt;/a&gt;）要让两个类型共享同一段底层内存。Go 为这些场景留了&#xA;一个逃生舱口：&lt;code&gt;unsafe&lt;/code&gt; 包。&lt;/p&gt;&#xA;&lt;p&gt;逃生舱口的代价是，一旦用它绕过类型系统，编译器与运行时原先提供的保证就部分失效，误用不再被&#xA;语言挡住，而要靠程序员自己遵守一组并不直观的规则。这一节先讲清 &lt;code&gt;unsafe.Pointer&lt;/code&gt; 的能力边界与&#xA;规范列出的合法模式，再讲清其中最隐蔽的一类陷阱（&lt;code&gt;uintptr&lt;/code&gt; 与垃圾回收的关系），最后讲编译器与&#xA;运行时如何用「指针检查器」（checkptr）把这类潜伏的误用变成当场报错。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1541-unsafepointer绕过类型系统的四条特权&#34;&gt;15.4.1 unsafe.Pointer：绕过类型系统的四条特权&lt;/h2&gt;&#xA;&lt;p&gt;普通指针 &lt;code&gt;*T&lt;/code&gt; 之间不能随意转换，类型系统不允许把 &lt;code&gt;*int&lt;/code&gt; 当作 &lt;code&gt;*float64&lt;/code&gt; 来读写。&lt;code&gt;unsafe.Pointer&lt;/code&gt;&#xA;是一种特殊指针，它在类型系统里开了一道口子，规范赋予它四条普通类型没有的特权：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;任意类型的指针 &lt;code&gt;*T&lt;/code&gt; 都可以转换为 &lt;code&gt;unsafe.Pointer&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;unsafe.Pointer&lt;/code&gt; 可以转换回任意类型的指针 &lt;code&gt;*T&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;uintptr&lt;/code&gt; 可以转换为 &lt;code&gt;unsafe.Pointer&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;unsafe.Pointer&lt;/code&gt; 可以转换为 &lt;code&gt;uintptr&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;前两条合起来，意味着借道 &lt;code&gt;unsafe.Pointer&lt;/code&gt; 可以把任意 &lt;code&gt;*T1&lt;/code&gt; 转成任意 &lt;code&gt;*T2&lt;/code&gt;，从而以另一种类型解释&#xA;同一段内存，这正是类型系统本想禁止的事。后两条让指针与整数互转，从而能对地址做算术。规范因此&#xA;明确写道：&lt;code&gt;Pointer&lt;/code&gt; 允许程序破坏类型系统、对任意内存读写，使用应格外小心。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// unsafe 包对 Pointer 的定义（ArbitraryType 仅用于文档，表示任意类型）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ArbitraryType&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ArbitraryType&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;口子开得这么大，规范并不是说怎么用都行。它列出了若干「合法的转换模式」，承诺只有落在这些模式&#xA;里的用法才有定义，偏离则是未定义行为。&lt;code&gt;go vet&lt;/code&gt; 会检查代码是否落在这些模式内，没过 &lt;code&gt;go vet&lt;/code&gt; 的&#xA;&lt;code&gt;unsafe&lt;/code&gt; 代码不受任何保证。下面逐一过这些模式，它们覆盖了 &lt;code&gt;unsafe&lt;/code&gt; 几乎全部的正当用途。&lt;/p&gt;</description>
    </item>
    <item>
      <title>15.5 逃逸分析</title>
      <link>/zh-cn/part5toolchain/ch15compile/escape/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch15compile/escape/</guid>
      <description>&lt;h1 id=&#34;155-逃逸分析&#34;&gt;15.5 逃逸分析&lt;/h1&gt;&#xA;&lt;p&gt;Go 程序员从不手动决定一个变量放栈还是放堆，这件事由编译器的&lt;strong&gt;逃逸分析&lt;/strong&gt;（escape analysis）&#xA;自动完成。它是 Go 性能的隐形功臣：把尽可能多的对象留在栈上，能大幅减轻垃圾回收&#xA;（&lt;a href=&#34;/zh-cn/part4memory/ch13gc/&#34;&gt;13 垃圾回收&lt;/a&gt;）的负担。这一节讲清它怎么判断、怎么实现、为何重要。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1551-逃逸决定栈还是堆&#34;&gt;15.5.1 逃逸：决定栈还是堆&lt;/h2&gt;&#xA;&lt;p&gt;核心问题：一个变量该分配在&lt;strong&gt;栈&lt;/strong&gt;上（随函数返回自动消失，零 GC 成本），还是&lt;strong&gt;堆&lt;/strong&gt;上&#xA;（生命周期不定，由 GC 管理）？判据是&lt;strong&gt;生命周期&lt;/strong&gt;：若一个变量的引用在函数返回后仍可能被用到，&#xA;它就不能放栈上（栈帧已随返回销毁），必须&lt;strong&gt;逃逸&lt;/strong&gt;到堆。逃逸分析就是静态地回答这个问题，&#xA;判断「这个变量的地址会不会跑出它所在函数的作用域」。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;go/cmd/compile/internal/escape&lt;/code&gt; 把这件事说成两条必须维持的不变量：（1）指向栈对象的指针&#xA;&lt;strong&gt;不能被存入堆&lt;/strong&gt;；（2）指向栈对象的指针&lt;strong&gt;不能活过该对象本身&lt;/strong&gt;，比如声明它的函数已经返回、&#xA;栈帧被销毁，或同一段栈空间在循环的不同迭代中被复用给了逻辑上不同的变量。只要一个变量的地址&#xA;有可能违反这两条，它就被判定逃逸，改为堆分配。&lt;/p&gt;&#xA;&lt;p&gt;最直接的观察手段是 &lt;code&gt;go build -gcflags=-m&lt;/code&gt;，它让编译器把每一处逃逸判断打印出来。把下面这段&#xA;喂给它（加 &lt;code&gt;-l&lt;/code&gt; 关掉内联，让输出聚焦在逃逸本身）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;ret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 返回局部变量的地址&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ go build -gcflags=&amp;#39;-m -l&amp;#39; demo.go&#xA;./demo.go:1:18: moved to heap: x&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;x&lt;/code&gt; 本是个普通局部变量，但它的地址被 &lt;code&gt;return&lt;/code&gt; 带出了函数，调用方拿到的指针必须在 &lt;code&gt;ret&lt;/code&gt; 返回&#xA;后依然有效，于是 &lt;code&gt;x&lt;/code&gt; 被「搬到堆上」（moved to heap）。这就是最典型的一类逃逸：&lt;code&gt;return &amp;amp;x&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1552-典型的逃逸场景&#34;&gt;15.5.2 典型的逃逸场景&lt;/h2&gt;&#xA;&lt;p&gt;逃逸的根源只有一个：地址外泄到了一个活得比本函数久的地方。但它在代码里有几副不同的面孔，&#xA;值得逐一认清。&lt;/p&gt;</description>
    </item>
    <item>
      <title>15.6 cgo</title>
      <link>/zh-cn/part5toolchain/ch15compile/cgo/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch15compile/cgo/</guid>
      <description>&lt;h1 id=&#34;156-cgo&#34;&gt;15.6 cgo&lt;/h1&gt;&#xA;&lt;p&gt;「cgo is not Go.」这是 Rob Pike 在一篇博客里给 cgo 下的判词。它道破了一件容易被忽略的事：&#xA;当你在 Go 源文件里 &lt;code&gt;import &amp;quot;C&amp;quot;&lt;/code&gt;，写下一行 &lt;code&gt;C.foo()&lt;/code&gt; 时，你已经踏出了 Go 这门语言为你&#xA;划定的世界，进入了另一个由 C 的 ABI、C 的栈、C 的内存模型构成的世界。cgo 是这两个世界之间的&#xA;桥，桥很有用，但过桥要付通行费，而且费用不低。&lt;/p&gt;&#xA;&lt;p&gt;这一节不逐行翻译 &lt;code&gt;runtime/cgocall.go&lt;/code&gt;，而是回答三个问题：为什么从 Go 调一个 C 函数会比调一个&#xA;Go 函数昂贵一两个数量级；运行时为这一次跨界究竟做了哪些事；以及由此衍生出的那条「Go 指针不可&#xA;被 C 长期持有」的规则从何而来。读完它，「cgo is not Go」这句话的分量就具体了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1561-两个世界的落差&#34;&gt;15.6.1 两个世界的落差&lt;/h2&gt;&#xA;&lt;p&gt;要理解 cgo 的代价，先要看清桥的两端有多么不同。Go 与 C 在四件根本的事情上各行其是。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;调用约定不同。&lt;/strong&gt; Go 有自己的寄存器调用约定（&lt;a href=&#34;../../../part1overview/ch02asm/callconv&#34;&gt;2.2&lt;/a&gt;），&#xA;参数与返回值的传递、栈帧的布局、哪些寄存器由调用方保存，都与 C 在该平台上的 ABI 不一致。&#xA;跨界调用必须先把参数按 C 的 ABI 重新摆放，把栈指针对齐到 C 要求的边界。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;栈不同。&lt;/strong&gt; Go 的 goroutine 栈是&lt;strong&gt;可增长&lt;/strong&gt;的（&lt;a href=&#34;/zh-cn/part4memory/ch14stack/&#34;&gt;14&lt;/a&gt;），初始只有&#xA;几 KB，靠编译器在函数入口插入的栈检查在需要时拷贝、搬迁整条栈。C 函数对此一无所知，它假定自己&#xA;跑在一条固定的、足够大的系统栈上，绝不会被搬走。让 C 代码跑在一条随时可能被移动的 goroutine&#xA;小栈上是灾难性的，因此跨界前必须切换到 M 的系统栈 &lt;code&gt;g0&lt;/code&gt;，那是一条由操作系统分配、不会增长也&#xA;不会搬迁的栈。&lt;/p&gt;</description>
    </item>
    <item>
      <title>15.7 过去、现在与未来</title>
      <link>/zh-cn/part5toolchain/ch15compile/future/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch15compile/future/</guid>
      <description>&lt;h1 id=&#34;157-过去现在与未来&#34;&gt;15.7 过去、现在与未来&lt;/h1&gt;&#xA;&lt;p&gt;编译器是 Go 工具链里改动最频繁、却对用户最透明的部分。同一份源码一行不改，换一个新版本重新&#xA;编译，往往就更快、更小、更优,你甚至不知道中间发生了什么。这一节把镜头拉远，回看编译器自身&#xA;走过的路，再看它当下在做什么、接下来会往哪里走。贯穿始终的，是一条不变的价值排序:&lt;strong&gt;编译速度&#xA;优先，生成代码质量其次，而两者都要在「可工程化」的前提下取舍&lt;/strong&gt;&#xA;（&lt;a href=&#34;../../../part1overview/ch01intro/history&#34;&gt;1.1&lt;/a&gt;）。&lt;/p&gt;&#xA;&lt;p&gt;值得先说清楚的是，本节谈的「变」全部发生在引擎盖之下。Go 对语言与工具的兼容性承诺，意味着这些&#xA;重构不该惊动任何一行用户代码。下面要讲的两次大重写，正是在这个约束下完成的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1571-过去从-c-到-go从-plan-9-到-ssa&#34;&gt;15.7.1 过去:从 C 到 Go，从 Plan 9 到 SSA&lt;/h2&gt;&#xA;&lt;p&gt;编译器自身经历了两次伤筋动骨的大变，方向却截然不同:一次换的是实现语言，一次换的是后端架构。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;其一，实现语言:C → Go。&lt;/strong&gt; 最早（到 Go 1.4 为止）的 &lt;code&gt;gc&lt;/code&gt; 编译器是用 C 写的，沿用了 Plan 9&#xA;工具链的代码风格与构建方式。Go 1.5 完成了&lt;strong&gt;自举&lt;/strong&gt;(bootstrap):编译器被机器翻译成 Go，从此&#xA;是「用 Go 写的 Go 编译器」。这件事的细节见 &lt;a href=&#34;../../../part1overview/ch03life/bootstrap&#34;&gt;3.3&lt;/a&gt;,&#xA;这里只强调它的意义。换语言不是为了赶时髦:C 版本无法利用 Go 自己的并发、内存安全与丰富的标准&#xA;库，也无法让 Go 社区用熟悉的语言去读、去改编译器。自举之后，编译器才真正成为「社区可维护的&#xA;Go 程序」，这为后续一切重构铺平了路。代价是引入了一个&lt;strong&gt;自举链&lt;/strong&gt;:要编译当前版本的 Go，先得有&#xA;一个能跑的旧版本 Go，工具链因此需要小心维护这条向后的依赖。&lt;/p&gt;&#xA;&lt;p&gt;顺带澄清一个常被混淆的命名:编译器叫 &lt;code&gt;gc&lt;/code&gt;，是 &amp;ldquo;Go compiler&amp;rdquo; 的缩写，与垃圾回收（大写 GC，&#xA;garbage collection）毫无关系。&lt;code&gt;cmd/compile/README&lt;/code&gt; 开篇就专门提醒了这一点。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;其二，后端架构:Plan 9 风格 → SSA。&lt;/strong&gt; 自举只是换了笔，没换骨架。Go 1.5/1.6 的后端仍大体&#xA;沿用 Plan 9 编译器的传统设计:基于一种较低层的指令表示直接做有限的优化与指派。这套后端能用，&#xA;但难以承载现代优化,它的中间表示不便于做数据流分析，加一个新优化往往要在多处特判，扩展性差。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.1 运行时死锁检查</title>
      <link>/zh-cn/part5toolchain/ch16tools/deadlock/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/deadlock/</guid>
      <description>&lt;h1 id=&#34;161-运行时死锁检查&#34;&gt;16.1 运行时死锁检查&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;fatal error: all goroutines are asleep - deadlock!&lt;/code&gt;,几乎每个 Go 程序员都见过这条错误。它来自&#xA;运行时内置的死锁检测器。这一节先讲清它怎么判定、何时被触发，再讲一件更要紧的事：它在理论上&#xA;能保证什么、又有什么是它&lt;strong&gt;结构性地检测不了&lt;/strong&gt;的。后者是许多线上「卡死」之谜的根源，理解了它，&#xA;比记住那条错误信息更有价值。&lt;/p&gt;&#xA;&lt;p&gt;我们先说结论，再看它如何从一段不到一百行的代码里得出。运行时的死锁检测器并不维护、也从不检查&#xA;一张「谁在等谁」的等待图（wait-for graph）。它只问两个极粗的问题：还有没有 M 在运行？将来有没有&#xA;计时器会触发？两个都答「没有」，它就宣告死锁。这种粗糙不是疏忽，它恰恰决定了检测器的能力边界,&#xA;本节后半会看到，正因为没有逐资源的等待边，它&lt;strong&gt;无法&lt;/strong&gt;在一部分 goroutine 之间找出一个死锁环。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1611-判据只数运行中的-m不看谁等谁&#34;&gt;16.1.1 判据：只数运行中的 M，不看谁等谁&lt;/h2&gt;&#xA;&lt;p&gt;死锁检测逻辑藏在 &lt;code&gt;runtime/proc.go&lt;/code&gt; 的 &lt;code&gt;checkdead&lt;/code&gt; 里&#xA;（&lt;a href=&#34;../../../part3concurrency/ch09sched/sysmon&#34;&gt;9.8&lt;/a&gt;）。它的判据可以一句话概括：&lt;strong&gt;没有任何线程&#xA;还在运行，且没有任何途径能让某个 goroutine 重新变得可运行，程序就死锁了。&lt;/strong&gt; 落到代码，「没有&#xA;线程在运行」是一次减法,用机器线程总数减去各类空闲与系统线程：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// checkdead：判定是否陷入全局死锁（裁剪后的速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 调用时必须持有 sched.lock。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;checkdead&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 作为 c-shared / c-archive 库被宿主程序嵌入时，没有运行中的 goroutine 是正常的,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 宿主仍在跑。panicking、cgo 额外 M 等情形也各自豁免（此处略）。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;islibrary&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isarchive&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GOARCH&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;wasm&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// run = 机器线程数 − 空闲 M − 持锁空闲 M − 系统 M，即「正在运行的 M」数。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// run0 通常为 0（cgo 额外 M 存在时为 1）。只要还有 M 在运行，就不可能&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 是全局死锁，立即返回。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;mcount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sched&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nmidle&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sched&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nmidlelocked&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sched&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nmsys&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ...（见下）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意这里没有出现任何「锁」「channel」「WaitGroup」的字样。&lt;code&gt;checkdead&lt;/code&gt; 不知道、也不关心每个&#xA;goroutine 究竟阻塞在哪个原语上,它只数还有几个 M 在跑。这是理解它全部行为的关键。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.2 竞争检查</title>
      <link>/zh-cn/part5toolchain/ch16tools/race/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/race/</guid>
      <description>&lt;h1 id=&#34;162-竞争检查&#34;&gt;16.2 竞争检查&lt;/h1&gt;&#xA;&lt;p&gt;数据竞争（data race）是并发程序里最隐蔽、最难复现的一类 bug。它的定义很短：两个 goroutine&#xA;并发访问同一内存地址、其中至少一个是写、且二者之间没有任何同步把它们排序，那么程序的行为&#xA;就是未定义的（&lt;a href=&#34;../../../part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;）。「未定义」不是「读到旧值」&#xA;这么温和，它意味着编译器与处理器都被允许做出令人意外的重排，结果可能时对时错，可能只在压力&#xA;负载下、只在某种 CPU 上、只在某个版本里偶发一次。靠人眼复查这种 bug 是没有指望的。Go 的&#xA;&lt;strong&gt;竞态检测器&lt;/strong&gt;（&lt;code&gt;-race&lt;/code&gt;）正是把这件事从「靠运气复现」变成「跑一遍就告诉你」的工具。这一节&#xA;讲清它的原理、它背后的算法代价、以及它那两条必须记住的能力边界。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1621-基于-happens-before-的动态检测&#34;&gt;16.2.1 基于 happens-before 的动态检测&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;go test -race&lt;/code&gt;、&lt;code&gt;go run -race&lt;/code&gt;、&lt;code&gt;go build -race&lt;/code&gt;、&lt;code&gt;go install -race&lt;/code&gt; 都能开启竞态检测。它的&#xA;内核是 Google 在 LLVM &lt;code&gt;compiler-rt&lt;/code&gt; 里维护的 &lt;strong&gt;ThreadSanitizer&lt;/strong&gt;（TSan）运行时，Go 以预编译的&#xA;&lt;code&gt;.syso&lt;/code&gt; 形式把它链进程序（见 &lt;code&gt;runtime/race/&lt;/code&gt;，因此 &lt;code&gt;-race&lt;/code&gt; 依赖 cgo）。它的工作原理是&#xA;&lt;strong&gt;动态的 happens-before 检测&lt;/strong&gt;：程序运行时，编译器在每一次内存访问前插入一个回调，运行时则&#xA;拦截每一个同步事件，二者合起来让 TSan 实时维护一张 happens-before 关系图（&lt;a href=&#34;../../../part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;&#xA;那条偏序）。当它发现两个 goroutine 访问了同一地址、至少一个是写、而二者之间&lt;strong&gt;不存在&lt;/strong&gt;任何&#xA;happens-before 边，就报告一个数据竞争。&lt;/p&gt;&#xA;&lt;p&gt;为什么「访问」与「同步」要分开拦截？因为这正是 happens-before 偏序的两个来源。访问告诉检测器&#xA;「谁在何时碰了哪块内存」，同步告诉它「哪两条时间线之间被建立了次序」。Go 在 &lt;code&gt;runtime/race.go&lt;/code&gt;&#xA;里暴露的这组钩子，恰好把这两类事件一一对应了出来：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;//go:build race&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 访问事件：编译器在每次读/写内存前自动插桩调用&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceRead&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceWrite&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceReadRange&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 切片、memmove 等成片访问&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceWriteRange&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 同步事件：在 addr 上建立跨 goroutine 的 happens-before 关系&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 注释原文：establish happens-before relations between goroutines&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceAcquire&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ≈ C11 atomic_load(acquire)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceRelease&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ≈ C11 atomic_store(release)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceReleaseMerge&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ≈ C11 atomic_exchange(release)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;RaceRead&lt;/code&gt;/&lt;code&gt;RaceWrite&lt;/code&gt; 是访问点，运行时里 channel 收发、&lt;code&gt;sync.Mutex&lt;/code&gt; 的加解锁、&lt;code&gt;sync/atomic&lt;/code&gt;&#xA;的每个原子操作、goroutine 的创建与退出，则在内部调用 &lt;code&gt;raceacquire&lt;/code&gt;/&lt;code&gt;racerelease&lt;/code&gt; 这对原语&#xA;把 happens-before 边「焊」上去。换句话说，&lt;a href=&#34;../../../part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt; 里&#xA;规定的每一条建立次序的语义（channel 发送 happens-before 对应接收完成、解锁 happens-before&#xA;后续加锁、原子写以 release 顺序同步原子读的 acquire），在 TSan 这里都落成一条具体的图上的边。&#xA;内存模型那句抽象的「无 happens-before 即竞争」，于是变成了一个能实际抓现行的判定。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.3 性能追踪</title>
      <link>/zh-cn/part5toolchain/ch16tools/trace/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/trace/</guid>
      <description>&lt;h1 id=&#34;163-性能追踪&#34;&gt;16.3 性能追踪&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;pprof&lt;/code&gt;（&lt;a href=&#34;.././perf&#34;&gt;16.5&lt;/a&gt;）回答的是「时间花在哪些函数上」。它把一段时间里的 CPU 采样聚合&#xA;成一棵调用树，告诉你 &lt;code&gt;json.Marshal&lt;/code&gt; 占了 30% 的 CPU。但它答不了另一类问题：一个请求耗了&#xA;50ms，其中 45ms 这个 goroutine &lt;strong&gt;根本没在跑&lt;/strong&gt;，它在等。等什么？等锁？等网络？被 GC 打断？&#xA;还是想跑却没有 P 可用（&lt;a href=&#34;../../../part3concurrency/ch09sched/steal&#34;&gt;9.2&lt;/a&gt;）？CPU 画像对「等待」&#xA;一无所知，因为它只采样正在执行的栈，一个阻塞的 goroutine 不在任何 CPU 上，自然不会被采到。&lt;/p&gt;&#xA;&lt;p&gt;回答「为什么等」要靠另一件工具，&lt;strong&gt;执行追踪器&lt;/strong&gt;（execution tracer）。它不做统计抽样，而是&#xA;&lt;strong&gt;逐条记录运行时事件&lt;/strong&gt;，按纳秒级时间戳排成一条时间线：哪个 goroutine 在哪一刻被创建、开始&#xA;跑、阻塞、被唤醒、结束;每个 P 何时开始与停止调度;每次 GC 走到哪个阶段;每个系统调用何时&#xA;进出。把这条时间线交给 &lt;code&gt;go tool trace&lt;/code&gt; 渲染，你能逐毫秒地看清程序里发生了什么。这一节讲它&#xA;记录什么、怎么用、以及它近年从「重而全」走向「轻而按需」的演进。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1631-追踪器记录什么运行时事件的时间线&#34;&gt;16.3.1 追踪器记录什么：运行时事件的时间线&lt;/h2&gt;&#xA;&lt;p&gt;执行追踪器内建在运行时里（&lt;code&gt;runtime/trace.go&lt;/code&gt;），它捕获的不是用户代码的函数调用，而是&#xA;&lt;strong&gt;运行时与调度器层面的状态转移&lt;/strong&gt;。&lt;code&gt;runtime/trace.go&lt;/code&gt; 的设计注释把这份事件清单列得很清楚：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;goroutine 生命周期&lt;/strong&gt;：创建（go 语句）、开始运行、阻塞、被唤醒、退出;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;阻塞的原因&lt;/strong&gt;：在 channel 收发、在 &lt;code&gt;sync.Mutex&lt;/code&gt;、在网络 I/O（netpoller，&lt;a href=&#34;../../../part3concurrency/ch09sched/poller&#34;&gt;9.9 网络轮询器&lt;/a&gt;）、在 &lt;code&gt;select&lt;/code&gt; 上各是不同的事件类型;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;每个 P 的调度活动&lt;/strong&gt;：P 何时启动、停止、被抢占;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;GC 相关事件&lt;/strong&gt;：标记开始、标记结束、各 STW（stop-the-world）停顿、堆大小的变化（&lt;a href=&#34;../../../part4memory/ch13gc/pacing&#34;&gt;13.3&lt;/a&gt;）;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;系统调用&lt;/strong&gt;：进入、返回、以及因系统调用阻塞而交还 P 的时刻。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;大多数事件都附带一个&lt;strong&gt;纳秒精度的时间戳&lt;/strong&gt;和一份&lt;strong&gt;栈回溯&lt;/strong&gt;。这意味着 trace 不仅告诉你「这里&#xA;发生了一次阻塞」，还告诉你「是哪一行代码、在哪个调用栈深处阻塞的」。把这些事件按 P、按&#xA;goroutine 在时间轴上铺开，&lt;code&gt;go tool trace&lt;/code&gt; 画出一张可交互的时间线，你能直接读出：这一刻有&#xA;几个 P 在干活、哪个 goroutine 跑在哪个 P 上、它为什么停下、GC 是不是正在和业务抢 CPU。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.4 代码测试</title>
      <link>/zh-cn/part5toolchain/ch16tools/testing/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/testing/</guid>
      <description>&lt;h1 id=&#34;164-代码测试&#34;&gt;16.4 代码测试&lt;/h1&gt;&#xA;&lt;p&gt;测试在 Go 里不是外挂，而是语言工具链的一等公民。&lt;code&gt;go test&lt;/code&gt; 内建，&lt;code&gt;testing&lt;/code&gt; 包标准，约定&#xA;取代配置。这套设计上的选择，深刻塑造了 Go 的工程文化：在别的语言里「要不要写测试、用哪个&#xA;框架」是一道需要权衡的决策，在 Go 里它是默认动作。这一节讲清这套机制如何运转、为何如此设计，&#xA;以及它从单元测试一路长到 Go 1.18 模糊测试的谱系。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1641-一等公民约定取代配置&#34;&gt;16.4.1 一等公民：约定取代配置&lt;/h2&gt;&#xA;&lt;p&gt;Go 的测试靠约定运转，几乎零配置。三条规则就是全部：测试文件以 &lt;code&gt;_test.go&lt;/code&gt; 结尾，测试函数&#xA;形如 &lt;code&gt;func TestXxx(t *testing.T)&lt;/code&gt;，与被测代码放在同一个包里。&lt;code&gt;go test&lt;/code&gt; 会自动发现并运行它们，&#xA;不需要 XML 配置、不需要外部 runner、不需要注解。一个项目无论多大，&lt;code&gt;go test ./...&lt;/code&gt; 一行就跑遍&#xA;全部测试：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// strings_test.go，与被测包 strings 同目录、同包&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;testing&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;TestIndex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;testing&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;T&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;got&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Index&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;chicken&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;ken&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;got&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Errorf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Index = %d, want 4&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;got&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 报告失败，但不中断&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;「约定优于配置」带来两层收益。其一是零门槛：写测试不需要先学一套框架，函数签名对了就能跑。&#xA;其二，也是更深远的一层，是整个生态的统一。所有 Go 项目的测试方式完全一致，于是 CI、覆盖率&#xA;工具、IDE、&lt;code&gt;go vet&lt;/code&gt; 都只需面对一种约定，无需为各色框架各写一套适配。这与别家形成鲜明对照：&#xA;Java 世界有 JUnit 4 / JUnit 5 / TestNG 之分，注解与 runner 各不相同；Python 有 &lt;code&gt;unittest&lt;/code&gt;、&#xA;&lt;code&gt;pytest&lt;/code&gt;、&lt;code&gt;nose&lt;/code&gt; 并存，发现规则与 fixture 机制互不兼容。框架的多样性把「跑某项目的测试」变成&#xA;一件需要先读文档的事。Go 用一套内建约定把这件事抹平到了零。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.5 基准测试与性能画像</title>
      <link>/zh-cn/part5toolchain/ch16tools/perf/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/perf/</guid>
      <description>&lt;h1 id=&#34;165-基准测试与性能画像&#34;&gt;16.5 基准测试与性能画像&lt;/h1&gt;&#xA;&lt;p&gt;优化的第一原则是先测量，再动手。凭直觉猜瓶颈往往猜错，真正的热点常常不在你以为的地方。Go 把&#xA;这套纪律所需的工具全部做进了工具链：基准测试（benchmark）回答「多快」，性能画像（profiling）&#xA;回答「慢在哪、内存花在哪」，而 &lt;code&gt;benchstat&lt;/code&gt; 用统计方法回答「这次改动到底是真的快了，还是只是&#xA;噪声」。这一节把这三件工具讲透，并把它们串成一条「画像找热点 → 改 → 基准验证」的测量驱动闭环,&#xA;这条闭环再往上，便与 PGO（&lt;a href=&#34;../../ch15compile/optimize&#34;&gt;15.3&lt;/a&gt;）和执行追踪&#xA;（&lt;a href=&#34;.././trace&#34;&gt;16.3&lt;/a&gt;）合流，构成 Go 完整的性能工程方法论。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1651-基准测试让多快可复现&#34;&gt;16.5.1 基准测试：让「多快」可复现&lt;/h2&gt;&#xA;&lt;p&gt;基准测试用 &lt;code&gt;func BenchmarkXxx(b *testing.B)&lt;/code&gt; 写，&lt;code&gt;go test -bench&lt;/code&gt; 运行。它要解决的核心难题是&#xA;&lt;strong&gt;测量精度&lt;/strong&gt;：单次调用一个纳秒级函数，时钟分辨率与调用开销会把信号淹没。Go 的办法是把待测代码&#xA;放进循环跑很多次，再除以次数得到「每次操作的耗时」（ns/op）。但循环要跑多少次？跑少了不稳定，&#xA;跑多了浪费时间。早期写法是手写一个从 &lt;code&gt;0&lt;/code&gt; 到 &lt;code&gt;b.N&lt;/code&gt; 的循环，框架&lt;strong&gt;自动反复调整 &lt;code&gt;b.N&lt;/code&gt;、多跑几轮&lt;/strong&gt;，&#xA;直到测量窗口足够长、结果足够稳定为止：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;BenchmarkFib&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;testing&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;N&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 框架反复加大 b.N 直到耗时稳定&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;fib&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;30&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这套 &lt;code&gt;b.N&lt;/code&gt; 机制朴素可靠，却有两处长期为人诟病的坑。其一是&lt;strong&gt;计时边界&lt;/strong&gt;：若基准函数里有昂贵的&#xA;准备工作，它会被算进每次测量，得手动 &lt;code&gt;b.ResetTimer()&lt;/code&gt;/&lt;code&gt;b.StopTimer()&lt;/code&gt; 把它摘出去（见&#xA;&lt;a href=&#34;/zh-cn/part5toolchain/ch16tools/perf/#1653-%e5%9f%ba%e5%87%86%e6%b5%8b%e8%af%95%e7%9a%84%e4%b8%a4%e4%b8%aa%e7%bb%8f%e5%85%b8%e9%99%b7%e9%98%b1&#34;&gt;16.5.3&lt;/a&gt;）。其二是&lt;strong&gt;死代码消除&lt;/strong&gt;：编译器看到 &lt;code&gt;fib(30)&lt;/code&gt; 的返回值&#xA;没人用，可能直接把整个调用优化掉，于是你测了个寂寞。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.6 运行时统计量</title>
      <link>/zh-cn/part5toolchain/ch16tools/metric/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/metric/</guid>
      <description>&lt;h1 id=&#34;166-运行时统计量&#34;&gt;16.6 运行时统计量&lt;/h1&gt;&#xA;&lt;p&gt;画像（&lt;a href=&#34;.././perf&#34;&gt;16.5&lt;/a&gt;）与追踪（&lt;a href=&#34;.././trace&#34;&gt;16.3&lt;/a&gt;）属于「出事时拉来诊断」的工具：它们成本&#xA;偏高、产出庞大，适合在你已经怀疑某处有问题时，针对一段时间或一次请求采下来细看。但生产环境&#xA;里更常见的问题不是「现在帮我剖析一下」，而是「这个服务过去一周健康吗、什么时候开始劣化的、&#xA;该不该半夜把人叫起来」。回答这类问题靠的不是一次性的深剖，而是&lt;strong&gt;持续的、低成本的、可长期&#xA;保留的数值&lt;/strong&gt;：堆有多大、GC 多频繁、goroutine 数量是否在涨、调度延迟的尾部有没有抬头。这类&#xA;数值就是&lt;strong&gt;运行时指标&lt;/strong&gt;（metrics）。&lt;/p&gt;&#xA;&lt;p&gt;指标和画像的区别，本质是「聚合 vs 明细」与「常驻 vs 按需」。画像把每一次分配、每一段 CPU&#xA;归因到具体调用栈，信息量大、采集贵；指标只保留聚合后的标量或分布，单次读取近乎免费，因而&#xA;可以每隔几秒采一次、连续采上几个月。这一节讲清 Go 暴露指标的两套接口、它们的演进，以及&#xA;指标如何接入完整的可观测性体系。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1661-从-memstats-到-runtimemetrics&#34;&gt;16.6.1 从 MemStats 到 runtime/metrics&lt;/h2&gt;&#xA;&lt;p&gt;历史上，程序读运行时内存指标的唯一入口是 &lt;code&gt;runtime.ReadMemStats&lt;/code&gt;，它填充一个 &lt;code&gt;MemStats&lt;/code&gt;&#xA;结构（&lt;a href=&#34;../../../part4memory/ch12alloc/mstats&#34;&gt;12.8&lt;/a&gt;）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// runtime.MemStats：字段写死在结构体里（节选）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;MemStats&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Alloc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 当前存活对象占用的堆字节&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;HeapInuse&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 含已用 span 的堆字节&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;NumGC&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 已完成的 GC 轮数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PauseTotalNs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 累计 STW 停顿（纳秒）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PauseNs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;256&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 最近 256 次停顿的环形缓冲&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ... 还有约三十个字段&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runtime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;MemStats&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runtime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;ReadMemStats&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 读取时需短暂 stop-the-world&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这套接口有三处硬伤。其一，字段&lt;strong&gt;写死&lt;/strong&gt;在结构体里：运行时想暴露一个新指标，就得往&#xA;&lt;code&gt;MemStats&lt;/code&gt; 里加字段、改公开 API，受 Go 兼容性承诺约束，加得很慎重，于是许多内部状态根本&#xA;没有出口。其二，&lt;code&gt;ReadMemStats&lt;/code&gt; 为了取得自洽快照，读取时要短暂 &lt;strong&gt;stop-the-world&lt;/strong&gt;，在高频&#xA;采集下成本不可忽略。其三，停顿信息只有一个 &lt;code&gt;PauseNs&lt;/code&gt; 环形数组和累计值，想知道「停顿的 P99&#xA;是多少」得自己从原始数组里算，而那个数组只存最近 256 次，早就漏掉了长期分布。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.7 语言服务协议</title>
      <link>/zh-cn/part5toolchain/ch16tools/gopls/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/gopls/</guid>
      <description>&lt;h1 id=&#34;167-语言服务协议&#34;&gt;16.7 语言服务协议&lt;/h1&gt;&#xA;&lt;p&gt;自动补全、跳转定义、实时报错、重构,现代编辑器里这些功能，在 Go 这边由 &lt;strong&gt;&lt;code&gt;gopls&lt;/code&gt;&lt;/strong&gt;（Go language&#xA;server，读作「go please」）提供。它背后是&lt;strong&gt;语言服务器协议&lt;/strong&gt;（Language Server Protocol，LSP）。&#xA;本节讲清三件事：LSP 解开的那个组合爆炸结、gopls 如何建立在编译器前端的可复用库之上、以及它&#xA;为何称得上 Go 工具链思想的集大成者。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1671-lsp解开-mn-的结&#34;&gt;16.7.1 LSP：解开 M×N 的结&lt;/h2&gt;&#xA;&lt;p&gt;设想没有 LSP 的世界。要让 &lt;strong&gt;M 个编辑器&lt;/strong&gt;都支持 &lt;strong&gt;N 种语言&lt;/strong&gt;的智能功能，朴素的做法是为每一对&#xA;「编辑器 × 语言」单独写一套集成：VS Code 要懂 Go、懂 Rust、懂 Python，Vim 也要各懂一遍,补全、&#xA;跳转、报错的逻辑在每一对里都重写。代价是 &lt;strong&gt;M×N&lt;/strong&gt; 套实现，且任何一门语言的语义更新，都要在&#xA;M 个编辑器里各自跟进。这条增长曲线注定走不远。&lt;/p&gt;&#xA;&lt;p&gt;LSP 把它解成 &lt;strong&gt;M+N&lt;/strong&gt;。微软在 2016 年 6 月为 Visual Studio Code 提出这套协议(后与 Red Hat、&#xA;Codenvy 合作开放，现已成行业标准)。它的关键是定义&lt;strong&gt;一套与语言、与编辑器都无关的标准协议&lt;/strong&gt;：&#xA;每门语言只需写&lt;strong&gt;一个&lt;/strong&gt;语言服务器（实现协议），每个编辑器只需写&lt;strong&gt;一个&lt;/strong&gt; LSP 客户端,此后任意&#xA;编辑器配任意语言，自动打通。语言服务器是一个独立进程，编辑器通过标准输入输出或套接字与它通信。&lt;/p&gt;&#xA;&lt;p&gt;协议本身建立在 &lt;strong&gt;JSON-RPC&lt;/strong&gt; 之上,只有三类消息：客户端发&lt;strong&gt;请求&lt;/strong&gt;（request）等服务器回&lt;strong&gt;响应&lt;/strong&gt;&#xA;（response），以及单向的&lt;strong&gt;通知&lt;/strong&gt;（notification）。位置用「文档 URI + 行列号」表示，刻意避开任何&#xA;语言特有的抽象（如某语言的 AST 节点），这正是它能跨语言通用的前提。一次「跳转到定义」的往返&#xA;是这样：&lt;/p&gt;</description>
    </item>
    <item>
      <title>17.1 依赖管理的难点</title>
      <link>/zh-cn/part5toolchain/ch17modules/challenges/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch17modules/challenges/</guid>
      <description>&lt;h1 id=&#34;171-依赖管理的难点&#34;&gt;17.1 依赖管理的难点&lt;/h1&gt;&#xA;&lt;p&gt;现代软件几乎不再从零写起。一个寻常的服务端程序，&lt;code&gt;import&lt;/code&gt; 列表往下走一层是日志库、HTTP&#xA;路由、序列化器，再往下是它们各自依赖的加密、压缩、网络原语,真正属于你自己的代码，可能不到&#xA;整张依赖图的百分之一。这带来了复用的红利，也带来了一个本不属于你、却必须由你回答的问题：&#xA;&lt;strong&gt;这张图上每个包，到底该用哪个版本？&lt;/strong&gt; 这一节不急着给 Go 的答案，先把问题本身的难讲清楚。&#xA;难点有两条主线：一是&lt;strong&gt;版本如何选&lt;/strong&gt;（钻石依赖与约束求解），二是&lt;strong&gt;选定之后如何不漂移&lt;/strong&gt;&#xA;（可重现构建）。理解了这两条，才能看懂 &lt;a href=&#34;.././minimum&#34;&gt;17.3&lt;/a&gt; 那套「最小版本选择」为什么值得&#xA;专门设计，以及 Go 为走到那一步付出过什么代价。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1711-钻石依赖一个约束求解问题&#34;&gt;17.1.1 钻石依赖：一个约束求解问题&lt;/h2&gt;&#xA;&lt;p&gt;依赖管理的核心难题，集中体现在一种叫&lt;strong&gt;钻石依赖&lt;/strong&gt;（diamond dependency）的拓扑上。你的程序同时&#xA;依赖 A 和 B，而 A 和 B 又都依赖同一个 C,依赖图从顶点出发分成两支，又在 C 处重新汇合，画出来&#xA;正是一颗钻石。问题出在两支对 C 提出了&lt;strong&gt;不同的版本要求&lt;/strong&gt;：&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart TD&#xA;    APP[&amp;#34;你的程序 main&amp;#34;] --&amp;gt; A[&amp;#34;A v1.0&amp;#34;]&#xA;    APP --&amp;gt; B[&amp;#34;B v1.0&amp;#34;]&#xA;    A --&amp;gt;|&amp;#34;require C v1.2&amp;#34;| C12[&amp;#34;C v1.2&amp;#34;]&#xA;    B --&amp;gt;|&amp;#34;require C v1.5&amp;#34;| C15[&amp;#34;C v1.5&amp;#34;]&#xA;    C12 -.-&amp;gt;|&amp;#34;二者只能合一&amp;#34;| PICK{&amp;#34;链进哪个 C?&amp;#34;}&#xA;    C15 -.-&amp;gt; PICK&lt;/pre&gt;&#xA;&lt;p&gt;为什么不能让 A 用 C v1.2、B 用 C v1.5，各取所需？多数语言做不到。C 的两个版本通常导出同一个&#xA;包路径、同一组类型名，把两份链进同一个程序，链接器会撞上重复符号；即便链接器允许，C v1.2 的&#xA;&lt;code&gt;Token&lt;/code&gt; 和 C v1.5 的 &lt;code&gt;Token&lt;/code&gt; 在类型系统里也是两个互不兼容的类型,一旦 A 把自己拿到的 &lt;code&gt;Token&lt;/code&gt;&#xA;传给 B，就会在边界上炸开。这条裂缝用一段示意代码看得更清楚：&lt;/p&gt;</description>
    </item>
    <item>
      <title>17.2 语义化版本管理</title>
      <link>/zh-cn/part5toolchain/ch17modules/semantics/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch17modules/semantics/</guid>
      <description>&lt;h1 id=&#34;172-语义化版本管理&#34;&gt;17.2 语义化版本管理&lt;/h1&gt;&#xA;&lt;p&gt;要管理版本，先得让版本号&lt;strong&gt;有意义&lt;/strong&gt;。&lt;a href=&#34;.././challenges&#34;&gt;17.1&lt;/a&gt; 把依赖管理的难处归到两点：钻石依赖&#xA;里的版本冲突，与跨机器、跨时间的可重现构建。这两点能不能解，取决于一个更底层的前提，&lt;strong&gt;一个版本&#xA;号到底承诺了什么&lt;/strong&gt;。如果 &lt;code&gt;v1.5&lt;/code&gt; 与 &lt;code&gt;v1.4&lt;/code&gt; 之间的关系完全无从预料，那么任何选择算法都无从下手，&#xA;只能退回到「把所有版本两两试一遍」的求解。Go 模块的做法，是先把版本号变成一份&lt;strong&gt;可被机器信任的&#xA;契约&lt;/strong&gt;，再在这份契约之上提出一条独特而强硬的规则,&lt;strong&gt;语义化导入版本&lt;/strong&gt;。这一节讲清这两者，它们是&#xA;&lt;a href=&#34;.././minimum&#34;&gt;17.3&lt;/a&gt; 那套版本选择算法能够成立、且能简单到「一遍图遍历」的前提。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1721-语义化版本把兼容性写进数字&#34;&gt;17.2.1 语义化版本：把兼容性写进数字&lt;/h2&gt;&#xA;&lt;p&gt;语义化版本（Semantic Versioning，semver）由 Tom Preston-Werner 在 2013 年定形为 2.0.0&#xA;规范，Go 模块直接采用了它，并在版本号前统一冠以 &lt;code&gt;v&lt;/code&gt;。版本号形如 &lt;strong&gt;&lt;code&gt;vMAJOR.MINOR.PATCH&lt;/code&gt;&lt;/strong&gt;&#xA;（如 &lt;code&gt;v1.4.2&lt;/code&gt;），三段都是非负整数，且每一段的递增都&lt;strong&gt;有规范约定的含义&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;PATCH&lt;/strong&gt;（补丁）递增：「只引入向后兼容的 bug 修复」。&lt;code&gt;v1.4.1 → v1.4.2&lt;/code&gt;，行为不变，只是更正了&#xA;错误。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;MINOR&lt;/strong&gt;（次版本）递增：「以向后兼容的方式向公开 API 增加了功能」。&lt;code&gt;v1.4 → v1.5&lt;/code&gt;，老代码&#xA;原封不动仍能编译、仍能运行，只是多了可用的东西。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;MAJOR&lt;/strong&gt;（主版本）递增：「引入了向后不兼容的 API 变更」。&lt;code&gt;v1.x → v2.0&lt;/code&gt;，老代码可能编译不过，&#xA;或行为悄然改变。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;把这套约定落到一个具体改动上，递增哪一段是被改动的性质唯一决定的，并无自由裁量：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;现有版本 v1.4.2，下一次发布该打什么版本号？&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  修了一个 panic，不动任何签名         → v1.4.3   （PATCH：兼容的修复）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  新增一个导出函数 Encode，旧 API 不变  → v1.5.0   （MINOR：兼容的新功能）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  删掉 Decode、或改了它的参数类型        → v2.0.0   （MAJOR：破坏兼容）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意每升一段，更低的段都归零：&lt;code&gt;v1.4.2&lt;/code&gt; 的次版本一升就是 &lt;code&gt;v1.5.0&lt;/code&gt; 而非 &lt;code&gt;v1.5.2&lt;/code&gt;，主版本一升就是&#xA;&lt;code&gt;v2.0.0&lt;/code&gt;。这条归零规则保证了「同一主版本下，版本号越大、含的兼容功能越全」，&lt;a href=&#34;.././minimum&#34;&gt;17.3&lt;/a&gt;&#xA;的「取最大」才有明确语义。&lt;/p&gt;</description>
    </item>
    <item>
      <title>17.3 最小版本选择算法</title>
      <link>/zh-cn/part5toolchain/ch17modules/minimum/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch17modules/minimum/</guid>
      <description>&lt;h1 id=&#34;173-最小版本选择算法&#34;&gt;17.3 最小版本选择算法&lt;/h1&gt;&#xA;&lt;p&gt;确定了语义化版本（&lt;a href=&#34;.././semantics&#34;&gt;17.2&lt;/a&gt;）后，还剩最后一问：当依赖图里各模块要求同一个依赖的&#xA;不同版本时，到底&lt;strong&gt;选哪个版本&lt;/strong&gt;？Go 的答案是一个反直觉、却异常简洁的算法,&lt;strong&gt;最小版本选择&lt;/strong&gt;&#xA;（Minimal Version Selection, MVS）。它是 Go 模块最具特色、也最值得玩味的设计：别家包管理器把&#xA;这件事做成了一个需要约束求解器的难题，Go 却把它压成了一遍图遍历。本节先把规则讲清，再看它为何&#xA;能这么简单，最后把它放回整个领域的谱系里，看清它究竟换走了什么、换来了什么。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1731-选满足要求的最低版本&#34;&gt;17.3.1 选「满足要求的最低版本」&lt;/h2&gt;&#xA;&lt;p&gt;MVS 的规则简单到令人意外：对每个依赖，选&lt;strong&gt;所有要求中最高的那个最低要求&lt;/strong&gt;,换句话说，选择&#xA;&lt;strong&gt;能满足所有模块要求的最低版本&lt;/strong&gt;。先用本书一贯的小例子建立直觉。你的程序直接要求 C ≥ v1.2;&#xA;依赖 A 要求 C ≥ v1.3;依赖 B 要求 C ≥ v1.1。三个要求里最高的下限是 v1.3,MVS 就选 &lt;strong&gt;C v1.3&lt;/strong&gt;。&#xA;注意这里的关键：哪怕此刻 C 已经发布了 v1.9，MVS &lt;strong&gt;也不选 v1.9&lt;/strong&gt;,它只选「满足所有要求的最低&#xA;版本」，即 v1.3。&lt;/p&gt;&#xA;&lt;p&gt;把这条规则放进一张真实的依赖图，就是 Go 官方文档采用的例子。主模块要求 A ≥ v1.2、B ≥ v1.2;&#xA;A v1.2 要求 C ≥ v1.3;B v1.2 要求 C ≥ v1.4;而 C v1.3 与 C v1.4 都要求 D ≥ v1.2。MVS 从主模块&#xA;出发，沿 &lt;code&gt;require&lt;/code&gt; 边走遍整张图，为每个模块记下「见过的最高要求」：&lt;/p&gt;</description>
    </item>
    <item>
      <title>17.4 vgo 与 dep 之争</title>
      <link>/zh-cn/part5toolchain/ch17modules/fight/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch17modules/fight/</guid>
      <description>&lt;h1 id=&#34;174-vgo-与-dep-之争&#34;&gt;17.4 vgo 与 dep 之争&lt;/h1&gt;&#xA;&lt;p&gt;今天的 Go 模块（Go Modules）并非一开始就有。它从一场颇具戏剧性、也颇有争议的社区事件里&#xA;诞生：&lt;strong&gt;vgo 与 dep 之争&lt;/strong&gt;。这段历史不只是八卦，它折射出开源项目治理、技术决策与社区情绪之间&#xA;的真实张力，是理解 Go 模块「为何是今天这样」的最后一块拼图，也是这本书一路谈下来的设计哲学&#xA;在工具链层面的一次集中演出。&lt;/p&gt;&#xA;&lt;p&gt;前面两节已经把 Go 模块的技术内核摆出来了：语义化版本管理（&lt;a href=&#34;.././semantics&#34;&gt;17.2&lt;/a&gt;）把主版本&#xA;写进导入路径，最小版本选择（&lt;a href=&#34;.././minimum&#34;&gt;17.3&lt;/a&gt;）用一条确定性算法挑出每个依赖的版本，二者&#xA;合起来，让 Go 既不需要 lock 文件、也不需要约束求解器。这一节回答的是另一个问题：为什么是&#xA;&lt;strong&gt;这套&lt;/strong&gt;方案，而不是当时社区已经投入两年、几乎被默认为「未来」的那一套。要讲清楚这个，得把&#xA;时间拨回到 2016 年。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1741-dep社区的官方实验&#34;&gt;17.4.1 dep：社区的「官方实验」&lt;/h2&gt;&#xA;&lt;p&gt;GOPATH 时代的依赖混乱（&lt;a href=&#34;.././challenges&#34;&gt;17.1&lt;/a&gt;）催生过一大批第三方工具：&lt;code&gt;godep&lt;/code&gt;、&lt;code&gt;glide&lt;/code&gt;、&#xA;&lt;code&gt;govendor&lt;/code&gt;，各自为政，互不兼容。2016 年，Go 团队决定收口，成立了一个依赖管理工作组，由&#xA;Sam Boyer 等社区成员牵头，做出一个被定位为&lt;strong&gt;官方实验&lt;/strong&gt;（official experiment）的工具：&#xA;&lt;strong&gt;&lt;code&gt;dep&lt;/code&gt;&lt;/strong&gt;。它不是某个人的玩具，而是 Go 团队背书、社区深度参与、被广泛理解为「将来会并入&#xA;&lt;code&gt;go&lt;/code&gt; 命令」的候选方案。许多公司和个人据此调整了工程实践，把项目迁到 dep 上。&lt;/p&gt;&#xA;&lt;p&gt;技术上，dep 走的是当时&lt;strong&gt;主流&lt;/strong&gt;的路子，与 Rust 的 Cargo、Ruby 的 Bundler、JavaScript 的 npm&#xA;一脉相承：一个清单文件声明你想要的版本范围，一个 &lt;strong&gt;lock 文件&lt;/strong&gt;钉死本次解析出的精确版本，中间&#xA;夹着一个&lt;strong&gt;约束求解器&lt;/strong&gt;。求解器要做的事，本质是布尔可满足性（SAT）：把「A 需要 B 的 &lt;code&gt;&amp;gt;=1.2&lt;/code&gt;」&#xA;「C 需要 B 的 &lt;code&gt;&amp;lt;2.0&lt;/code&gt;」这类约束翻译成逻辑公式，搜一组同时满足所有人的版本赋值出来。&lt;/p&gt;</description>
    </item>
    <item>
      <title>18.1 跨越 FFI 边界</title>
      <link>/zh-cn/part6hetero/ch18gpu/boundary/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch18gpu/boundary/</guid>
      <description>&lt;h1 id=&#34;181-跨越-ffi-边界&#34;&gt;18.1 跨越 FFI 边界&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;../../../part5toolchain/ch15compile/cgo&#34;&gt;15.6&lt;/a&gt;已经把 cgo 这座桥拆开看过了：一次从 Go 到 C 的&#xA;调用要切到 &lt;code&gt;g0&lt;/code&gt; 系统栈、按 C 的 ABI 重摆参数、&lt;code&gt;entersyscall&lt;/code&gt; 让出 P、调用、再 &lt;code&gt;exitsyscall&lt;/code&gt;&#xA;抢回一个 P，整套下来比一次 Go 调用贵上一两个数量级。那一节给出的结论很干脆：cgo 适合&lt;strong&gt;少量、&#xA;粗粒度&lt;/strong&gt;的调用，最忌讳放进热点循环里反复跨界。&lt;/p&gt;&#xA;&lt;p&gt;把这条结论摆到 GPU 面前，矛盾立刻就尖锐了。GPU 编程的本质，恰恰是&lt;strong&gt;频繁地跨界&lt;/strong&gt;。一次最普通的&#xA;推理或渲染，CPU 这侧要做的事无非三类：把数据从主机内存拷到显存、启动一个又一个 kernel、再把&#xA;结果拷回来。每一类都是一次离开 Go、进入驱动的边界穿越。一个稍有规模的神经网络有成百上千个算子，&#xA;逐个朴素地启动，就是成百上千次 cgo 调用串在一条关键路径上。15.6 说「别在紧循环里跨界」，&#xA;而 GPU 的工作负载天生就长在这样一个紧循环里。这一节要回答的就是：当跨界无法避免、且必须高频时，&#xA;这道边界该怎么设计才不至于被通行费压垮。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1811-边界的另一端一个异步的命令世界&#34;&gt;18.1.1 边界的另一端：一个异步的命令世界&lt;/h2&gt;&#xA;&lt;p&gt;先看清桥的对岸站着谁。从 Go 调一个 C 库函数，对岸是一段同步执行的 C 代码，调用返回时活儿就干完了。&#xA;GPU 不是这样。CPU 这侧调用的并不是「计算本身」，而是&lt;strong&gt;向设备下达一条命令&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;以 CUDA 为例，软件栈分了两层。底层是&lt;strong&gt;驱动 API&lt;/strong&gt;（driver API，&lt;code&gt;libcuda&lt;/code&gt;，前缀 &lt;code&gt;cu&lt;/code&gt;），直接对应&#xA;内核态驱动暴露的能力；上层是&lt;strong&gt;运行时 API&lt;/strong&gt;（runtime API，&lt;code&gt;libcudart&lt;/code&gt;，前缀 &lt;code&gt;cuda&lt;/code&gt;），把驱动&#xA;API 包装得更易用，自动管理上下文与模块。无论走哪一层，CPU 侧的一次 &lt;code&gt;cudaLaunchKernel&lt;/code&gt; 或&#xA;&lt;code&gt;cudaMemcpyAsync&lt;/code&gt; 都只是把一条命令塞进一个叫&lt;strong&gt;流&lt;/strong&gt;（stream）的队列，然后&lt;strong&gt;立即返回&lt;/strong&gt;。真正的&#xA;计算由 GPU 在自己的时间线上异步地完成。CPU 下令，GPU 干活，两者在不同的时钟上跑。&lt;/p&gt;</description>
    </item>
    <item>
      <title>18.2 调度器与阻塞的外部调用</title>
      <link>/zh-cn/part6hetero/ch18gpu/sched/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch18gpu/sched/</guid>
      <description>&lt;h1 id=&#34;182-调度器与阻塞的外部调用&#34;&gt;18.2 调度器与阻塞的外部调用&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././boundary&#34;&gt;18.1&lt;/a&gt; 给出的药方是「异步、少同步」：把命令压进流，立即返回，只在末尾等一次。&#xA;可那「末尾的一次」终归要等。&lt;code&gt;cudaStreamSynchronize&lt;/code&gt; 会一直阻塞，直到 GPU 把整条流排空；一次&#xA;同步的 &lt;code&gt;cudaMemcpy&lt;/code&gt;,一次没走异步路径的驱动调用，都会让 Go 这侧的线程实打实地停在 C 里。&#xA;本节要问的就是：当一次跨界&lt;strong&gt;真的会长时间阻塞&lt;/strong&gt;时，&lt;a href=&#34;/zh-cn/part3concurrency/ch09sched/&#34;&gt;第 9 章&lt;/a&gt;&#xA;那套调度机器会怎样反应？它会不会被一个卡在 GPU 上的调用拖垮？&lt;/p&gt;&#xA;&lt;p&gt;答案要从两个方向看。Go 阻塞在 C 里是一个方向，C 反过来回调 Go 是另一个方向，调度器在这两个&#xA;方向上各有一套应对。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1821-一次阻塞的跨界调度器看见的是什么&#34;&gt;18.2.1 一次阻塞的跨界，调度器看见的是什么&lt;/h2&gt;&#xA;&lt;p&gt;先回忆第 9 章的图景：调度器在 M（系统线程）、P（逻辑处理器，数量受 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 限制）、&#xA;G（goroutine）三者上编排并发，一个 M 必须先绑定一个 P 才能运行 Go 代码。&lt;/p&gt;&#xA;&lt;p&gt;15.6 已经讲过，&lt;code&gt;cgocall&lt;/code&gt; 在跨界前会调用 &lt;code&gt;entersyscall&lt;/code&gt;。这一步的意义此刻变得关键：&#xA;&lt;strong&gt;在调度器的记账里，一次 cgo 调用和一次系统调用是同一回事&lt;/strong&gt;。M 被标记为「正处于系统调用中」，&#xA;它承载的 goroutine 转入 &lt;code&gt;_Gsyscall&lt;/code&gt; 状态。这里有一个随版本演进的实现细节值得一提：早先的&#xA;运行时还专门给 P 设过一个 &lt;code&gt;_Psyscall&lt;/code&gt; 状态来表示「这个 P 正陷在系统调用里」，但 &lt;strong&gt;Go 1.26 起&#xA;这个 P 状态已退役&lt;/strong&gt;（源码里留作 &lt;code&gt;_Psyscall_unused&lt;/code&gt;），改为直接看 goroutine 的状态来判断一个 P&#xA;是否在系统调用中。这次精简并非无关紧要，它顺带把每次 cgo 跨界的固定开销削减了约三成,正是&#xA;&lt;a href=&#34;.././boundary&#34;&gt;18.1.2&lt;/a&gt; 那「第一笔成本」被运行时自己磨薄的一例。从这一刻起到 C 调用返回，&#xA;有一条铁律：&lt;/p&gt;</description>
    </item>
    <item>
      <title>18.3 显存与垃圾回收的分界</title>
      <link>/zh-cn/part6hetero/ch18gpu/memory/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch18gpu/memory/</guid>
      <description>&lt;h1 id=&#34;183-显存与垃圾回收的分界&#34;&gt;18.3 显存与垃圾回收的分界&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;../../../part5toolchain/ch15compile/cgo&#34;&gt;15.6&lt;/a&gt; 讲过 cgo 的指针规则：Go 的对象不归 C 管，&#xA;GC 随时可能搬走或回收它，所以 C 不得在调用返回后还持有一个未钉住的 Go 指针。那是从「Go 与 C&#xA;两块内存」的二元世界推出来的。GPU 把这张地图复杂化了：现在至少有&lt;strong&gt;四种&lt;/strong&gt;内存，分属不同的管辖，&#xA;遵守不同的规矩。这一节要先把这张地图画清楚，再看垃圾回收器与它们各自的分界划在哪里，&#xA;以及哪一条分界最容易在异步传输里被踩穿。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1831-一张内存地图&#34;&gt;18.3.1 一张内存地图&lt;/h2&gt;&#xA;&lt;p&gt;一个用 Go 驱动 GPU 的程序，运行时面对的内存大致分四块：&lt;/p&gt;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart TB&#xA;    subgraph host[&amp;#34;主机内存（CPU 可寻址）&amp;#34;]&#xA;        go[&amp;#34;Go 堆&amp;lt;br/&amp;gt;GC 管理：可移动、可回收、被扫描&amp;#34;]&#xA;        c[&amp;#34;C 堆 / malloc&amp;lt;br/&amp;gt;手动管理：GC 不可见&amp;#34;]&#xA;        pinned[&amp;#34;页锁定内存 cudaHostAlloc&amp;lt;br/&amp;gt;不可换出，供 DMA 高速传输&amp;#34;]&#xA;    end&#xA;    subgraph device[&amp;#34;设备内存（GPU 上，CPU 不可寻址）&amp;#34;]&#xA;        dev[&amp;#34;显存 cudaMalloc&amp;lt;br/&amp;gt;GC 完全看不见，手动 cudaFree&amp;#34;]&#xA;    end&#xA;    go -. &amp;#34;cudaMemcpy&amp;#34; .-&amp;gt; dev&#xA;    pinned -. &amp;#34;cudaMemcpyAsync（快）&amp;#34; .-&amp;gt; dev&lt;/pre&gt;&#xA;&lt;p&gt;四块里，只有第一块 &lt;strong&gt;Go 堆&lt;/strong&gt;归 GC 管，第 12、13 章那套分配与回收、那套可达性扫描，作用范围就到&#xA;这里为止。后三块对 GC 而言是「境外」：C 堆是手动 &lt;code&gt;malloc&lt;/code&gt;/&lt;code&gt;free&lt;/code&gt; 的，页锁定内存由 CUDA 分配，&#xA;而&lt;strong&gt;显存根本不在 CPU 的地址空间里&lt;/strong&gt;,CPU 连解引用它都做不到。理解这一节的全部诀窍，&#xA;就是时刻分清一个指针到底落在哪一块。&lt;/p&gt;</description>
    </item>
    <item>
      <title>18.4 异步编程模型</title>
      <link>/zh-cn/part6hetero/ch18gpu/model/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch18gpu/model/</guid>
      <description>&lt;h1 id=&#34;184-异步编程模型&#34;&gt;18.4 异步编程模型&lt;/h1&gt;&#xA;&lt;p&gt;前三节都在讲 FFI 边界上的「成本」：过桥要快（&lt;a href=&#34;.././boundary&#34;&gt;18.1&lt;/a&gt;）、过桥会占住线程&#xA;（&lt;a href=&#34;.././sched&#34;&gt;18.2&lt;/a&gt;）、桥上的内存归谁管（&lt;a href=&#34;.././memory&#34;&gt;18.3&lt;/a&gt;）。这一节换一个角度，&#xA;回到&lt;strong&gt;并发模型&lt;/strong&gt;本身。Go 的并发是 goroutine 与通道，GPU 的并发是另一套东西，CPU 自己还藏着&#xA;第三套。把这三套并行摆清楚、看明白它们怎么对接，是这一章的收尾，也是理解「何时该把活儿推过边界、&#xA;何时压根不必」的关键。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1841-三种并行别混为一谈&#34;&gt;18.4.1 三种并行，别混为一谈&lt;/h2&gt;&#xA;&lt;p&gt;「并发」（concurrency）与「并行」（parallelism）的区分，第 9 章借 Rob Pike 的话讲过：并发是&#xA;&lt;strong&gt;把程序拆成可独立推进的部分&lt;/strong&gt;的结构，并行是&lt;strong&gt;同时执行&lt;/strong&gt;的事实。带着这把尺子，眼前这三套各占&#xA;什么位置就清楚了。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;goroutine 并发。&lt;/strong&gt; Go 的看家本领，M:N 的&lt;strong&gt;任务级&lt;/strong&gt;并发。每个 goroutine 是一段独立的控制流，&#xA;廉价、可阻塞、靠通道通信。它回答的是「如何把程序组织成许多并发的任务」。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;SIMT（GPU）。&lt;/strong&gt; Single Instruction, Multiple Thread。一次 kernel 启动铺开一张由成千上万个&#xA;线程组成的网格，它们&lt;strong&gt;跑同一段程序、各自处理一个数据元素&lt;/strong&gt;，硬件以 warp（NVIDIA 上 32 个线程&#xA;一组）为单位近乎锁步地推进。它回答的是「如何让海量数据元素被同一段计算并行碾过」。&#xA;编程模型是：为单个元素写好 kernel，然后启动一整张网格。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;SIMD（CPU）。&lt;/strong&gt; Single Instruction, Multiple Data。在&lt;strong&gt;一个&lt;/strong&gt; CPU 核内，一条指令同时作用于&#xA;一个向量寄存器里的多个数据通道（4、8、16 路）。它是 CPU 自带的&lt;strong&gt;数据级&lt;/strong&gt;并行，不需要 GPU、&#xA;不跨任何边界。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这三者是&lt;strong&gt;正交的轴&lt;/strong&gt;，可以叠加：一个 Go 程序完全可以用 goroutine 并发地处理多个请求，&#xA;在每个请求的 CPU 内循环里用 SIMD 向量化，再把最重的矩阵乘用 SIMT 推给 GPU。把它们混为一谈&#xA;（比如「一个 GPU 线程就像一个 goroutine」）会从一开始就把设计带偏:goroutine 是为&lt;strong&gt;可阻塞的&#xA;任务&lt;/strong&gt;生的，GPU 线程是为&lt;strong&gt;无分支的密集算术&lt;/strong&gt;生的，两者的设计假设南辕北辙。&lt;/p&gt;</description>
    </item>
    <item>
      <title>19.1 渲染管线与 Go 的位置</title>
      <link>/zh-cn/part6hetero/ch19graphics/pipeline/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch19graphics/pipeline/</guid>
      <description>&lt;h1 id=&#34;191-渲染管线与-go-的位置&#34;&gt;19.1 渲染管线与 Go 的位置&lt;/h1&gt;&#xA;&lt;p&gt;第 18 章把 GPU 当作一台「通用并行计算设备」来读。但 GPU 的本名是 &lt;strong&gt;Graphics&lt;/strong&gt; Processing Unit,&#xA;它最初、也最本职的工作是渲染图形。早在「用 GPU 做通用计算」成为口号之前的二十年，显卡就已经在&#xA;为屏幕上的每一个像素做并行运算了。所以图形是&lt;strong&gt;最古老的异构负载&lt;/strong&gt;,GPU 上那套大规模并行的硬件，&#xA;本就是为它而生。这一章回到这条本源，看 Go 在图形里扮演什么角色，而起点是看清那条贯穿一切的&#xA;&lt;strong&gt;渲染管线&lt;/strong&gt;,以及 Go 的代码究竟坐在它的哪个位置。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1911-管线一条分段的数据流&#34;&gt;19.1.1 管线：一条分段的数据流&lt;/h2&gt;&#xA;&lt;p&gt;把一堆三维顶点变成屏幕上一帧彩色图像，GPU 走的是一条&lt;strong&gt;分段的流水线&lt;/strong&gt;。每一段读入上一段的输出，&#xA;做一类固定的变换，再交给下一段。经典的图形管线大致是这样：&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart LR&#xA;    app[&amp;#34;应用阶段&amp;lt;br/&amp;gt;(CPU / Go)&amp;#34;] --&amp;gt; vs[&amp;#34;顶点处理&amp;lt;br/&amp;gt;(可编程 shader)&amp;#34;]&#xA;    vs --&amp;gt; pa[&amp;#34;图元装配&amp;lt;br/&amp;gt;(固定)&amp;#34;]&#xA;    pa --&amp;gt; rs[&amp;#34;光栅化&amp;lt;br/&amp;gt;(固定)&amp;#34;]&#xA;    rs --&amp;gt; fs[&amp;#34;片元处理&amp;lt;br/&amp;gt;(可编程 shader)&amp;#34;]&#xA;    fs --&amp;gt; fb[&amp;#34;帧缓冲&amp;lt;br/&amp;gt;(固定)&amp;#34;]&lt;/pre&gt;&#xA;&lt;p&gt;这条管线里，有些段是&lt;strong&gt;固定功能&lt;/strong&gt;的（图元装配、光栅化、帧缓冲混合），由硬件写死，你只能配置参数；&#xA;有些段是&lt;strong&gt;可编程&lt;/strong&gt;的，顶点处理与片元处理各跑一段叫 &lt;strong&gt;shader&lt;/strong&gt;(着色器）的小程序，由你提供。&#xA;光栅化是这条线的心脏：它把一个三角形「填」成一片覆盖到的像素，决定了哪些片元需要被着色。&#xA;整条管线天然适合 GPU,因为每个顶点、每个片元都可以被同一段 shader 独立地、并行地处理,&#xA;这正是第 18 章说的 SIMT。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1912-go-坐在哪里cpu-侧的编排者&#34;&gt;19.1.2 Go 坐在哪里：CPU 侧的编排者&lt;/h2&gt;&#xA;&lt;p&gt;关键的问题来了：这条管线上，Go 的代码坐在哪一段？&lt;/p&gt;&#xA;&lt;p&gt;答案是&lt;strong&gt;最左边那一段，且仅此一段&lt;/strong&gt;:应用阶段。Go 跑在 CPU 上，它做的事是「准备数据、下达命令」:&#xA;把场景的顶点、纹理、变换矩阵组织好，上传到显存，然后发起一次次&lt;strong&gt;绘制调用&lt;/strong&gt;(draw call),&#xA;告诉 GPU「用这套数据、这套 shader，画」。一旦绘制调用发出，后面那几段顶点处理、光栅化、片元处理&#xA;全在 GPU 上跑，Go 不再介入，只在最后需要时把结果取回或交给窗口系统显示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>19.2 图形绑定与线程亲和</title>
      <link>/zh-cn/part6hetero/ch19graphics/bindings/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch19graphics/bindings/</guid>
      <description>&lt;h1 id=&#34;192-图形绑定与线程亲和&#34;&gt;19.2 图形绑定与线程亲和&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././pipeline&#34;&gt;19.1&lt;/a&gt; 说 Go 坐在管线的应用阶段，负责发起绘制调用。可在真正发出第一条绘制调用&#xA;之前，有一道坎横在所有 Go 图形程序面前，它不来自图形本身，而来自 Go 的并发模型与图形 API 的&#xA;一个根本矛盾:&lt;strong&gt;图形上下文绑定线程，而 goroutine 会迁移线程&lt;/strong&gt;。这道坎是本节的主角，&#xA;而 &lt;a href=&#34;../../ch18gpu/sched&#34;&gt;18.2.5&lt;/a&gt; 那把钥匙 &lt;code&gt;LockOSThread&lt;/code&gt;,在这里从一个可选的技巧变成了必需。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1921-上下文一个绑定线程的隐式状态机&#34;&gt;19.2.1 上下文：一个绑定线程的隐式状态机&lt;/h2&gt;&#xA;&lt;p&gt;OpenGL 这类图形 API 是围绕&lt;strong&gt;上下文&lt;/strong&gt;(context)组织的。上下文是一个庞大的隐式状态机:当前绑定&#xA;的着色器、纹理、缓冲、混合模式、视口……几乎所有 API 调用都不显式地接收上下文参数，而是隐式地&#xA;作用在「当前上下文」上。&lt;code&gt;glBindTexture&lt;/code&gt; 绑的是当前上下文里的纹理槽，&lt;code&gt;glDrawElements&lt;/code&gt; 用的是当前&#xA;上下文里的一整套状态。&lt;/p&gt;&#xA;&lt;p&gt;关键在于「当前」二字是&lt;strong&gt;按线程&lt;/strong&gt;定义的:一个 OpenGL 上下文在某个时刻「当前于」某一条特定的 OS&#xA;线程。你在线程 A 上把上下文设为当前、配置好状态、发出绘制调用，这一切都依附在线程 A 上。&#xA;如果同一串 OpenGL 调用里，有一部分跑到了线程 B 上,而线程 B 上并没有这个当前上下文,那些调用&#xA;要么直接失败，要么作用在一个空的上下文上，画面一片漆黑。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1922-goroutine-会迁移于是必须钉住&#34;&gt;19.2.2 goroutine 会迁移，于是必须钉住&lt;/h2&gt;&#xA;&lt;p&gt;这正是 Go 的并发模型撞上图形 API 的地方。回忆第 9 章:goroutine 不绑定固定的线程，调度器会&#xA;把它在不同的 M 之间&lt;strong&gt;迁移&lt;/strong&gt;,这次在线程 A 上跑，一次抢占、一次系统调用、一次通道阻塞之后，&#xA;下次很可能就被调度到线程 B 上继续。对纯 Go 代码，这种迁移是透明的、无害的，正是 M:N 调度的&#xA;红利。可对 OpenGL,它是灾难:&lt;/p&gt;</description>
    </item>
    <item>
      <title>19.3 软件渲染与并行</title>
      <link>/zh-cn/part6hetero/ch19graphics/software/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch19graphics/software/</guid>
      <description>&lt;h1 id=&#34;193-软件渲染与并行&#34;&gt;19.3 软件渲染与并行&lt;/h1&gt;&#xA;&lt;p&gt;前两节的渲染都要过一道边界:把数据和命令交给 GPU,付清第 18 章那一整套过桥费，还要伺候图形上下文&#xA;的线程纪律（&lt;a href=&#34;.././bindings&#34;&gt;19.2&lt;/a&gt;）。这一节走另一条路:&lt;strong&gt;软件渲染&lt;/strong&gt;,完全在 CPU 上算出每一个像素，&#xA;不碰 GPU、不碰驱动、不碰任何 FFI 边界。这条路一度被认为是「慢而无用的退路」,可它恰恰是把 Go&#xA;的并发能力，以及 Go 1.27 的 &lt;code&gt;simd&lt;/code&gt;,用在图形上的最佳舞台。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1931-为什么还要软件渲染&#34;&gt;19.3.1 为什么还要软件渲染&lt;/h2&gt;&#xA;&lt;p&gt;GPU 这么快，为什么还有人在 CPU 上渲染?因为有几类场景，GPU 要么用不上，要么不划算。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;没有 GPU 可用。&lt;/strong&gt; 服务器端批量生成图片、缩略图、图表、PDF 渲染，跑在没有显卡、也没有显示器&#xA;的无头（headless）机器上。这是 Go 最主流的部署形态，恰恰也是 GPU 最缺席的地方。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;要确定性与可移植。&lt;/strong&gt; 软件渲染的结果逐位可复现，不受驱动版本、显卡型号的影响。需要「同一份&#xA;输入在任何机器上渲染出逐像素相同的图」时(测试基线、文档生成），软件渲染是唯一可靠的选择。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;图省心、要全控。&lt;/strong&gt; 没有上下文、没有线程纪律、没有边界，渲染器就是一段普通的 Go 代码，&#xA;可读、可调试、可单步,每一个像素怎么来的都看得见。Go 标准库的 &lt;code&gt;image&lt;/code&gt;、&lt;code&gt;image/draw&lt;/code&gt;、&#xA;&lt;code&gt;golang.org/x/image&lt;/code&gt;,以及社区里的纯 Go 渲染器（如 &lt;code&gt;polyred&lt;/code&gt;),走的都是这条路。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;把这几条收成一句:软件渲染是&lt;strong&gt;边界的另一面&lt;/strong&gt;。第 18、19 章前面反复计算的过桥成本，在这里一笔&#xA;都不存在，代价是放弃了 GPU 的海量吞吐。于是问题从「怎么过桥更便宜」变成了「&lt;strong&gt;不过桥，怎么把&#xA;CPU 的并行榨干&lt;/strong&gt;」。答案有两层，正好对应第 18.4 节那个三种并行的分类里属于 CPU 的两种:&#xA;goroutine 的任务并行，与 SIMD 的数据并行。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1932-把屏幕切成瓦片goroutine-级并行&#34;&gt;19.3.2 把屏幕切成瓦片：goroutine 级并行&lt;/h2&gt;&#xA;&lt;p&gt;软件渲染有一个先天的好性质:&lt;strong&gt;像素之间大多互不依赖&lt;/strong&gt;。一帧图像上不同区域的像素，可以完全独立地&#xA;算出来。这是教科书级的「易并行」(embarrassingly parallel)问题,而 Go 的 goroutine 正是为这种&#xA;任务级并行生的。&lt;/p&gt;</description>
    </item>
    <item>
      <title>19.4 浏览器中的渲染</title>
      <link>/zh-cn/part6hetero/ch19graphics/wasm/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch19graphics/wasm/</guid>
      <description>&lt;h1 id=&#34;194-浏览器中的渲染&#34;&gt;19.4 浏览器中的渲染&lt;/h1&gt;&#xA;&lt;p&gt;前三节的渲染，要么把活儿推过 FFI 边界交给本机 GPU(19.1、19.2),要么留在 CPU 上软件渲染&#xA;（&lt;a href=&#34;.././software&#34;&gt;19.3&lt;/a&gt;）。这一节把场景换到一个特别的运行环境:&lt;strong&gt;浏览器&lt;/strong&gt;。Go 可以编译成&#xA;WebAssembly(WASM)在浏览器里跑，而一旦进了浏览器，渲染会遇到一道全新的边界。有意思的是，&#xA;这道边界的形状、它的成本、应对它的办法，与前面整整两章讲的异构计算几乎一一对应,只是搬高了一层。&#xA;看懂这一节，就会发现「FFI 边界」是个比 cgo 宽得多的母题。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1941-go-进入浏览器wasm-与-syscalljs&#34;&gt;19.4.1 Go 进入浏览器：WASM 与 syscall/js&lt;/h2&gt;&#xA;&lt;p&gt;Go 用 &lt;code&gt;GOOS=js GOARCH=wasm&lt;/code&gt; 就能把程序编译成一个 &lt;code&gt;.wasm&lt;/code&gt; 模块，加载进网页，在浏览器的&#xA;WebAssembly 虚拟机里执行。但 WASM 模块本身是个&lt;strong&gt;沙盒&lt;/strong&gt;:它能做纯计算，却&lt;strong&gt;碰不到外面的世界&lt;/strong&gt;,&#xA;没有 DOM，没有画布，没有 GPU,这些都属于浏览器的 JavaScript 环境。&lt;/p&gt;&#xA;&lt;p&gt;WASM 与 JS 之间隔着一道膜，跨越它的桥是标准库的 &lt;code&gt;syscall/js&lt;/code&gt;。Go 代码通过 &lt;code&gt;js.Global()&lt;/code&gt; 拿到&#xA;JS 的全局对象，用 &lt;code&gt;js.Value&lt;/code&gt; 的 &lt;code&gt;Get&lt;/code&gt;/&lt;code&gt;Set&lt;/code&gt;/&lt;code&gt;Call&lt;/code&gt; 去读写 JS 属性、调用 JS 函数:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;syscall/js&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;doc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;js&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Global&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;document&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;canvas&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;doc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;getElementById&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;screen&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;canvas&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;getContext&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;2d&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 每一次 Get / Call，都是一次从 WASM 跨进 JS 的边界穿越&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;请认出这道膜的真面目:它就是又一条 &lt;strong&gt;FFI 边界&lt;/strong&gt;。&lt;code&gt;syscall/js&lt;/code&gt; 之于 WASM/JS,正如 cgo 之于&#xA;Go/C。每一次 &lt;code&gt;js.Value&lt;/code&gt; 的调用，都要把参数从 WASM 的线性内存里编组、跨过膜、进入 JS,&#xA;和 &lt;a href=&#34;../../ch18gpu/boundary&#34;&gt;18.1&lt;/a&gt; 描述的跨界是同构的。于是 18.1 那条核心告诫原封不动地适用:&#xA;&lt;strong&gt;这道边界穿越有固定成本，要尽量少跨。&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>20.1 推理运行时与 FFI</title>
      <link>/zh-cn/part6hetero/ch20inference/runtime/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch20inference/runtime/</guid>
      <description>&lt;h1 id=&#34;201-推理运行时与-ffi&#34;&gt;20.1 推理运行时与 FFI&lt;/h1&gt;&#xA;&lt;p&gt;第 18、19 章反复出现的那道 FFI 边界，到了大模型这里有了它最当下的一副面孔。训练大模型几乎是&#xA;Python 与 CUDA 的天下，可当一个模型训好、要被部署去&lt;strong&gt;服务&lt;/strong&gt;千万次请求时，舞台换了主角，&#xA;Go 在这一层站得很稳。这一章讲 Go 如何承担 AI 的推理与服务，而第一节要先解决最底层的问题:&#xA;Go 自己并不做矩阵乘，它得接入一个本地推理运行时,而这次接入，又是第 18 章那道边界。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2011-训练在-python推理与服务在哪&#34;&gt;20.1.1 训练在 Python，推理与服务在哪&lt;/h2&gt;&#xA;&lt;p&gt;先把分工看清。&lt;strong&gt;训练&lt;/strong&gt;是一件研究性的事:试不同的结构、调超参、看损失曲线，要的是表达的灵活与&#xA;生态的丰富，Python 加 PyTorch、再加 CUDA，是它无可争议的家园。Go 在训练这一侧没有位置，&#xA;也不必有。&lt;/p&gt;&#xA;&lt;p&gt;但&lt;strong&gt;推理与服务&lt;/strong&gt;是另一种事。模型已经冻结，权重不再变，剩下的问题清一色是&lt;strong&gt;系统问题&lt;/strong&gt;:怎么用&#xA;高吞吐、低延迟地服务大量并发请求;怎么把一个几 GB 的模型稳定地装进内存、喂给设备;怎么做成一个&#xA;能一键部署、抗住生产流量、好监控好运维的服务。把这串问题念一遍，会发现它正是 Go 被设计出来要&#xA;解决的那一类:静态编译的单文件部署、天生的并发、可控的内存、成熟的网络栈。所以「训练归 Python、&#xA;推理与服务归 Go」不是偶然的站队，而是两种工作的性质把两门语言各自吸到了它擅长的那一层。&#xA;当下最流行的本地大模型服务 Ollama,正是用 Go 写就的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2012-接入本地推理运行时又是那道边界&#34;&gt;20.1.2 接入本地推理运行时：又是那道边界&lt;/h2&gt;&#xA;&lt;p&gt;Go 站在服务层，可矩阵乘、注意力、量化这些真正吃算力的活儿，Go 并不亲自做,它把这些交给一个&#xA;&lt;strong&gt;本地推理运行时&lt;/strong&gt;:&lt;code&gt;llama.cpp&lt;/code&gt;/&lt;code&gt;ggml&lt;/code&gt;(C/C++)、ONNX Runtime、或厂商的运行时。这些运行时是&#xA;用 C/C++ 写的高度优化的张量计算库，能调度 CPU 的 SIMD、也能驱动 GPU。Go 要用它们，&#xA;靠的还是 cgo。&lt;/p&gt;&#xA;&lt;p&gt;于是第 18 章的整套机制原样回来了。Ollama 的文档说得很直白:它「包含用 CGO 编译的原生代码」,&#xA;原生推理引擎用 CMake 构建，按需编译出 CUDA、ROCm、Vulkan 等后端。这意味着 15.6 与第 18 章讲过的&#xA;代价一并继承:构建期需要 C/C++ 工具链、失去纯 Go 的轻便（&lt;a href=&#34;../../../part5toolchain/ch15compile/cgo&#34;&gt;15.6.4&lt;/a&gt;),&#xA;运行期每次跨界要付那笔状态转换的税。Ollama 文档甚至点出了一个 15.6 没细说的脆弱处:Go 与 C&#xA;两侧共享的数据结构「可能不同步，导致意外崩溃」,这正是 cgo 把两个世界缝在一起时，缝合处最阴险的&#xA;一类 bug。&lt;/p&gt;</description>
    </item>
    <item>
      <title>20.2 分词与张量</title>
      <link>/zh-cn/part6hetero/ch20inference/tokenize/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch20inference/tokenize/</guid>
      <description>&lt;h1 id=&#34;202-分词与张量&#34;&gt;20.2 分词与张量&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././runtime&#34;&gt;20.1&lt;/a&gt; 把权重与张量的家安顿在了原生运行时一侧，Go 只在边界上递句柄、搬小数据。&#xA;这一节深入那「小数据」本身:文本怎么变成模型能吃的数字，数字又怎么变回文本。这件看似琐碎的事，&#xA;藏着一个 Go 程序员会心一笑的细节,它几乎就是第 5 章「字符串是一段不可变字节」的直接应用，&#xA;而且一旦疏忽，就会在流式输出时吐出乱码。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2021-分词文本与模型之间的翻译层&#34;&gt;20.2.1 分词：文本与模型之间的翻译层&lt;/h2&gt;&#xA;&lt;p&gt;模型不认识文本。它的输入和输出都是&lt;strong&gt;整数&lt;/strong&gt;,词表里的 token 编号。把人类的文本与模型的整数互相&#xA;翻译的这一层，叫&lt;strong&gt;分词器&lt;/strong&gt;(tokenizer)。它做两件互逆的事:把输入字符串切成一串 token id（编码),&#xA;把模型生成的 token id 拼回字符串（解码)。&lt;/p&gt;&#xA;&lt;p&gt;现代大模型几乎都用&lt;strong&gt;字节对编码&lt;/strong&gt;(Byte-Pair Encoding, BPE)或其变体。它的思路是数据驱动的:&#xA;从最细的单位出发，统计语料里最常一起出现的相邻对，把高频对合并成一个新 token，反复合并，&#xA;最终得到一张几万项的词表，常见词是一个完整 token，生僻词则被拆成几个子词片段。这样既控制了&#xA;词表大小，又能表示任何输入，不会遇到「未登录词」。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2022-为什么是字节而不是字符第-5-章的回响&#34;&gt;20.2.2 为什么是字节，而不是字符：第 5 章的回响&lt;/h2&gt;&#xA;&lt;p&gt;这里有一个对 Go 程序员格外亲切的关键:当代主流的 BPE，是&lt;strong&gt;字节级&lt;/strong&gt;(byte-level)的。它的最小&#xA;单位不是 Unicode 字符（码点)，而是 &lt;strong&gt;UTF-8 字节&lt;/strong&gt;。词表里的合并，发生在字节序列上。&lt;/p&gt;&#xA;&lt;p&gt;这正是第 5 章反复强调的那件事:&lt;a href=&#34;../../../part2lang/ch05data/string&#34;&gt;5.2&lt;/a&gt; 说过，Go 的字符串&#xA;本质是一段&lt;strong&gt;不可变的字节序列&lt;/strong&gt;,&lt;code&gt;range&lt;/code&gt; 一个字符串得到的是码点（rune），而下标索引得到的是字节。&#xA;字节级 BPE 与 Go 的字符串模型严丝合缝:两者都把文本看作字节。于是把一段 Go 字符串喂给字节级&#xA;分词器，概念上不需要任何「字符」的中间层,它处理的就是字符串底层那串字节。&lt;/p&gt;&#xA;&lt;p&gt;但字节级也埋了一个雷，而这个雷恰好踩在 Go 的痛点上:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;一个 token 的边界，未必落在一个完整 UTF-8 字符的边界上。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;一个中文字符在 UTF-8 里占 3 个字节，一个 emoji 可能占 4 个。字节级 BPE 完全可能把这 3 个字节&#xA;&lt;strong&gt;拆进两个相邻的 token&lt;/strong&gt;。这在编码时无所谓，可在&lt;strong&gt;逐 token 解码&lt;/strong&gt;时就出事了:当模型先吐出&#xA;半个字符的那个 token，你拿到的是一串&lt;strong&gt;不完整的 UTF-8 字节&lt;/strong&gt;,它还不构成一个合法的码点，&#xA;要等下一个 token 到达、补齐剩下的字节，才拼得出那个字符。&lt;/p&gt;</description>
    </item>
    <item>
      <title>20.3 服务、批处理与流式</title>
      <link>/zh-cn/part6hetero/ch20inference/serving/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch20inference/serving/</guid>
      <description>&lt;h1 id=&#34;203-服务批处理与流式&#34;&gt;20.3 服务、批处理与流式&lt;/h1&gt;&#xA;&lt;p&gt;前两节把单次推理的底层铺好了:&lt;a href=&#34;.././runtime&#34;&gt;20.1&lt;/a&gt; 经 cgo 接入运行时、安顿好权重与张量,&#xA;&lt;a href=&#34;.././tokenize&#34;&gt;20.2&lt;/a&gt; 讲清了 token 进、token 出。可一个真实的服务，要同时伺候成千上万条这样的&#xA;请求，每条都在持续地吐 token。怎么把它们高效、稳定地组织起来，是一个彻头彻尾的&lt;strong&gt;并发与调度&lt;/strong&gt;&#xA;问题,而这正是 Go 的主场。这一节把第 10 章的通道、第 7 章的 context，落到大模型服务上。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2031-一个请求的一生一条-token-流&#34;&gt;20.3.1 一个请求的一生：一条 token 流&lt;/h2&gt;&#xA;&lt;p&gt;先看清一次生成的形状。大模型是&lt;strong&gt;自回归&lt;/strong&gt;的:它一次只生成一个 token，把这个 token 接回输入，&#xA;再算下一个，循环往复，直到生成结束符或达到长度上限。所以从时间轴上看，&lt;strong&gt;一个请求不是一次&#xA;请求-响应，而是一条随时间流出的 token 流&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;这个形状和 Go 的并发模型天造地设:一个 goroutine 跑生成循环，每算出一个 token 就往一个通道里送,&#xA;下游从通道里收，正是第 10 章「用通信共享内存」的标准句式。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 一个请求 = 一个生成 goroutine，把 token 逐个送进通道&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;generate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;chan&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runtime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;NextToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 一次 cgo 调用，算一个 token&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 送给下游（HTTP handler）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Done&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 客户端走了，立刻收手&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;IsEOS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这段骨架已经埋下了本节三个主题的种子:&lt;code&gt;out&lt;/code&gt; 通道是&lt;strong&gt;流式&lt;/strong&gt;,&lt;code&gt;ctx.Done()&lt;/code&gt; 是&lt;strong&gt;取消&lt;/strong&gt;,&#xA;而当 &lt;code&gt;out&lt;/code&gt; 满、&lt;code&gt;out &amp;lt;- tok&lt;/code&gt; 阻塞时就是&lt;strong&gt;背压&lt;/strong&gt;。下面逐一展开。&lt;/p&gt;</description>
    </item>
    <item>
      <title>21.1 Agent 控制循环</title>
      <link>/zh-cn/part6hetero/ch21agent/loop/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch21agent/loop/</guid>
      <description>&lt;h1 id=&#34;211-agent-控制循环&#34;&gt;21.1 Agent 控制循环&lt;/h1&gt;&#xA;&lt;p&gt;第 20 章讲的是 Go 服务一个模型,请求进、token 流出。可当下的系统往往更进一步:让模型不只回答，&#xA;还能&lt;strong&gt;调用工具、连成多步、自主推进&lt;/strong&gt;,直到把一个任务办完。这就是 &lt;strong&gt;AI Agent&lt;/strong&gt;。Agent 听起来很&#xA;玄,本书的立场一以贯之:把模型的智能搁在一边，只看运行时那一面。这样看，一个 Agent 不过是一个&#xA;&lt;strong&gt;控制循环&lt;/strong&gt;,而控制循环正是计算机科学最古老、最朴素的结构之一。这一章就用这个视角，&#xA;把 Agent 还原成一个 Go 程序员熟悉的并发问题。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2111-把-agent-还原成一个控制循环&#34;&gt;21.1.1 把 Agent 还原成一个控制循环&lt;/h2&gt;&#xA;&lt;p&gt;剥掉所有花哨的说法，一个 Agent 的核心是一个循环:&lt;/p&gt;&#xA;&#xA;&lt;script src=&#34;/mermaid.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;/mermaid-init.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;pre class=&#34;mermaid&#34;&gt;flowchart TD&#xA;    start[&amp;#34;收到任务&amp;#34;] --&amp;gt; ctx[&amp;#34;把对话历史 &amp;#43; 可用工具&amp;lt;br/&amp;gt;发给模型&amp;#34;]&#xA;    ctx --&amp;gt; model[&amp;#34;模型回应&amp;#34;]&#xA;    model --&amp;gt; branch{&amp;#34;要调用工具吗？&amp;#34;}&#xA;    branch --&amp;gt;|&amp;#34;否：给出最终答案&amp;#34;| done[&amp;#34;返回结果&amp;#34;]&#xA;    branch --&amp;gt;|&amp;#34;是：请求调用某工具&amp;#34;| tool[&amp;#34;执行工具&amp;#34;]&#xA;    tool --&amp;gt; append[&amp;#34;把工具结果&amp;lt;br/&amp;gt;追加进对话历史&amp;#34;]&#xA;    append --&amp;gt; ctx&lt;/pre&gt;&#xA;&lt;p&gt;把这张图读一遍:把上下文（对话历史加可用工具的描述)发给模型;模型要么给出最终答案（循环结束），&#xA;要么请求调用某个工具;若是后者，就执行那个工具，把结果追加回上下文，再发给模型,如此往复。&#xA;所谓「自主」「智能体」，机制上就是&lt;strong&gt;这个带工具调用的循环&lt;/strong&gt;。模型负责在每一步决定「下一步做什么」,&#xA;而 Agent 运行时负责忠实地执行这个循环:维护上下文、调度工具、把结果喂回去。&lt;/p&gt;&#xA;&lt;p&gt;这个认识是解魅的，也是解放的。它告诉我们:写一个 Agent 框架，&lt;strong&gt;不需要任何机器学习&lt;/strong&gt;,需要的是&#xA;把一个循环、一组工具、一份不断增长的上下文，组织得正确、并发、可靠,而这些全是本书前面讲过的&#xA;东西。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2112-它是一台状态机&#34;&gt;21.1.2 它是一台状态机&lt;/h2&gt;&#xA;&lt;p&gt;把这个循环看得再细一点，它其实是一台&lt;strong&gt;状态机&lt;/strong&gt;。每一轮，Agent 处在某个明确的状态里:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;思考中&lt;/strong&gt;:等模型返回（一次第 20 章意义上的推理请求，往往是流式的)。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;待执行工具&lt;/strong&gt;:模型已决定调用某工具，正准备或正在执行它。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;整合中&lt;/strong&gt;:工具有了结果，正把它追加进上下文。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;完成 / 失败&lt;/strong&gt;:循环终止。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;把这些状态&lt;strong&gt;显式&lt;/strong&gt;地写出来，而不是让它们隐式地散落在控制流里，在工程上有实打实的好处。其一，&#xA;&lt;strong&gt;可观测&lt;/strong&gt;:任一时刻都能报告「这个 Agent 卡在哪一步」,对调试和监控至关重要。其二，&lt;strong&gt;可持久化与&#xA;可恢复&lt;/strong&gt;:一个长跑的 Agent 任务可能要数分钟、调几十个工具，把状态显式化，就能把它存盘、在崩溃后&#xA;从断点续跑，而不是从头再来。其三，&lt;strong&gt;可控&lt;/strong&gt;:超时、重试、人工介入，都更容易挂在显式的状态转移上。&#xA;Go 没有花哨的状态机框架，但一个带 &lt;code&gt;state&lt;/code&gt; 字段的结构体加一个 &lt;code&gt;for&lt;/code&gt; 加一个 &lt;code&gt;switch&lt;/code&gt;,&#xA;朴素得正好:&lt;/p&gt;</description>
    </item>
    <item>
      <title>21.2 工具调用与 MCP</title>
      <link>/zh-cn/part6hetero/ch21agent/mcp/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch21agent/mcp/</guid>
      <description>&lt;h1 id=&#34;212-工具调用与-mcp&#34;&gt;21.2 工具调用与 MCP&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././loop&#34;&gt;21.1&lt;/a&gt; 把 Agent 还原成了一个控制循环，而循环里最关键的那个动作是「执行工具」。&#xA;这一节就专讲工具:一个工具在机制上是什么，模型怎么决定调用它，运行时怎么把调用分发到真正的代码，&#xA;以及当工具的数量与来源爆炸式增长时，&lt;strong&gt;Model Context Protocol&lt;/strong&gt;(MCP)如何用一套协议把这件事&#xA;标准化。看清这一层，会发现工具调用本质上就是一次结构化的远程过程调用,而 RPC 正是 Go 的老本行。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2121-工具是什么一个带-schema-的函数&#34;&gt;21.2.1 工具是什么：一个带 schema 的函数&lt;/h2&gt;&#xA;&lt;p&gt;剥开「工具」这个词，它不过是&lt;strong&gt;一个带类型描述的函数&lt;/strong&gt;,由三部分组成:一个名字、一段给模型看的&#xA;自然语言描述、一份参数的结构化 schema(通常是 JSON Schema)。模型在每一步看到的，就是这样一组&#xA;工具的名字、描述与参数格式;它据此决定要不要调、调哪个、传什么参数，然后输出一个&lt;strong&gt;结构化的调用&#xA;请求&lt;/strong&gt;:工具名加一组 JSON 参数。运行时接到这个请求，查到对应的函数，反序列化参数，执行，&#xA;再把结果序列化回去。&lt;/p&gt;&#xA;&lt;p&gt;把这个流程念一遍，它的形状就是 RPC:&lt;strong&gt;名字 + 序列化参数 → 执行 → 序列化结果&lt;/strong&gt;。在 Go 里，&#xA;工具最自然地落成一个接口:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Tool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;interface&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 分发用的键&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Description&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 给模型看的说明&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Schema&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RawMessage&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 参数的 JSON Schema&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Invoke&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RawMessage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RawMessage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 分发就是一次 map 查找加一次反序列化&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Agent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;callTool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;call&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ToolCall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RawMessage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tools&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Errorf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;unknown tool: %s&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Invoke&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ctx 一路透传，为 21.3 的取消埋好线&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意 &lt;code&gt;Invoke&lt;/code&gt; 的签名带着 &lt;code&gt;context.Context&lt;/code&gt;,这不是摆设:工具可能要发 HTTP、查数据库、读文件，&#xA;都是可能阻塞的 I/O,把 ctx 透传进去，21.3 讲的超时与取消才能一路抵达最底层。一个工具调用框架的&#xA;运行时核心，基本就是这个接口加这次 map 分发,又一次，没有一行机器学习。&lt;/p&gt;</description>
    </item>
    <item>
      <title>21.3 流式、背压与取消</title>
      <link>/zh-cn/part6hetero/ch21agent/stream/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part6hetero/ch21agent/stream/</guid>
      <description>&lt;h1 id=&#34;213-流式背压与取消&#34;&gt;21.3 流式、背压与取消&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;.././loop&#34;&gt;21.1&lt;/a&gt; 立起了 Agent 的控制循环，&lt;a href=&#34;.././mcp&#34;&gt;21.2&lt;/a&gt; 讲清了循环里的工具调用。这最后一节，&#xA;把第 7 章的 context 与第 10、11 章的并发原语请上台做全书的谢幕演出,因为一个生产级的 Agent，&#xA;真正的难处不在循环的骨架，而在贯穿整条链路的三件事:流式、背压、取消。它们彼此缠绕，&#xA;而把它们统一起来的，是一个 Go 程序员再熟悉不过的形状,一棵由 context 绑定的 goroutine 树。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2131-整条链路都是流式的&#34;&gt;21.3.1 整条链路都是流式的&lt;/h2&gt;&#xA;&lt;p&gt;先看清数据在 Agent 里怎么流。一个请求从客户端进来，落到 Agent 的控制循环;循环每一步向模型发起&#xA;一次推理，而那次推理是 &lt;a href=&#34;../../ch20inference/serving&#34;&gt;20.3&lt;/a&gt; 讲的&lt;strong&gt;流式 token 流&lt;/strong&gt;;循环还会调用&#xA;工具，工具自身也可能慢、可能流式返回。于是&lt;strong&gt;流式不是某一层的特性，而是贯穿整条链路的常态&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;这带来一个体验上的要求:用户不该盯着空白等 Agent 默默跑完几十步。模型在想什么、正调用哪个工具、&#xA;工具回了什么，都应当&lt;strong&gt;边发生边呈现&lt;/strong&gt;。这意味着 Agent 不只是消费下层的流，它还要&lt;strong&gt;再生产&lt;/strong&gt;一条&#xA;自己的事件流向上吐:把「模型正在输出的 token」「开始调用工具 X」「工具 X 返回了」这些事件，&#xA;实时地流给客户端。一条流进、一条流出，中间是控制循环,这又是第 10 章「goroutine 经通道把事件&#xA;逐个传出」的句式，只是这次传的是 Agent 的执行事件。&lt;/p&gt;&#xA;&lt;h2 id=&#34;2132-取消必须穿透每一层&#34;&gt;21.3.2 取消必须穿透每一层&lt;/h2&gt;&#xA;&lt;p&gt;流式之外，最硬的需求是&lt;strong&gt;取消&lt;/strong&gt;。用户随时可能点「停止」、可能关掉连接;一个上层任务失败，&#xA;正在并行跑的兄弟任务就该立刻停手。一次取消，必须能穿透 Agent 那一整摞调用层,而这正是第 7 章&#xA;&lt;code&gt;context&lt;/code&gt; 被设计出来要做的事。&lt;/p&gt;&#xA;&lt;p&gt;把一个 ctx 从最顶端（客户端请求）一路透传下去:进入控制循环，传给每一次 &lt;code&gt;model.Infer&lt;/code&gt;,&#xA;传给每一次 &lt;code&gt;tool.Invoke&lt;/code&gt;,再传进工具内部的每一次 HTTP、每一次数据库查询。这样，&#xA;顶端一个 &lt;code&gt;cancel()&lt;/code&gt;,&lt;code&gt;ctx.Done()&lt;/code&gt; 的信号就会同时抵达每一层:模型停止生成（释放 20.1 那块&#xA;每请求的 KV cache),在飞的工具 I/O 当即中止。整摞调用栈像多米诺一样干净地倒下，没有泄漏的&#xA;goroutine、没有继续空转烧钱的推理。&lt;/p&gt;</description>
    </item>
    <item>
      <title>结束语：Go 去向何方？</title>
      <link>/zh-cn/finalwords/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/finalwords/</guid>
      <description>&lt;h1 id=&#34;结束语go-去向何方&#34;&gt;结束语：Go 去向何方？&lt;/h1&gt;&#xA;&lt;p&gt;读到这里，读者已经跟随本书走过了 Go 的全景与历史、语言特性、并发、内存、编译器与工具链、&#xA;以及异构计算与 AI 六大部分。作为收尾，我们不妨跳出具体的实现细节，谈一谈 Go 留下的遗产、眼下面临的挑战，&#xA;以及它可能的去向。&lt;/p&gt;&#xA;&lt;h2 id=&#34;go-留下了什么&#34;&gt;Go 留下了什么&lt;/h2&gt;&#xA;&lt;p&gt;回望全书，Go 最深的一笔遗产，或许不是某一项具体技术，而是它反复示范的一种&lt;strong&gt;设计姿态&lt;/strong&gt;：&#xA;把复杂度安置在最不打扰用户的地方，让简单成为结果而非起点。这条主线在书中处处显形,调度器&#xA;把线程管理的复杂度（&lt;a href=&#34;.././part3concurrency/ch09sched/readme&#34;&gt;9&lt;/a&gt;）藏进运行时，只给用户一个&#xA;&lt;code&gt;go&lt;/code&gt; 关键字；垃圾回收（&lt;a href=&#34;.././part4memory/ch13gc/readme&#34;&gt;13&lt;/a&gt;）把亚毫秒停顿的全部精巧留在内部，&#xA;让用户几乎无需调参；泛型（&lt;a href=&#34;.././part2lang/ch08generics/readme&#34;&gt;8&lt;/a&gt;）等了十三年，只为找到一个&#xA;不牺牲编译速度与简单性的实现；模块系统（&lt;a href=&#34;.././part5toolchain/ch17modules/readme&#34;&gt;17&lt;/a&gt;）用最小&#xA;版本选择这一最简算法，换掉了业界惯用的约束求解器。每一处都在回答同一个问题：一项复杂度，&#xA;凭什么值得被引入？&lt;/p&gt;&#xA;&lt;p&gt;正是这种克制，让 Go 在诞生十余年后，依然能让用户写出向后兼容的程序，依然以「简单、工程&#xA;友好」立足于工业界。它证明了一件事：一门成功的语言，靠的未必是最强的表达力，而可能是最&#xA;清醒的取舍。&lt;/p&gt;&#xA;&lt;h2 id=&#34;它面临的挑战&#34;&gt;它面临的挑战&lt;/h2&gt;&#xA;&lt;p&gt;Go 并非没有代价。书中也一再点出它取舍的另一面：非移动式 GC 带来的内存碎片要靠尺寸类去兜&#xA;（&lt;a href=&#34;.././part4memory/ch12alloc/basic&#34;&gt;12.1&lt;/a&gt;）；满屏的 &lt;code&gt;if err != nil&lt;/code&gt;&#xA;（&lt;a href=&#34;.././part2lang/ch07errors/readme&#34;&gt;7&lt;/a&gt;）是「错误即值」的必然账单，而把它写短的种种语法尝试&#xA;至今未被接纳（&lt;a href=&#34;.././part2lang/ch07errors/future&#34;&gt;7.5&lt;/a&gt;）；泛型的字典间接在热路径上有时反而更慢&#xA;（&lt;a href=&#34;.././part2lang/ch08generics/future&#34;&gt;8.4&lt;/a&gt;）；cgo 的跨界开销让 Go 与 C 世界的互通远谈不上廉价&#xA;（&lt;a href=&#34;.././part5toolchain/ch15compile/cgo&#34;&gt;15.6&lt;/a&gt;）。这些都不是 bug，而是设计取舍刻下的边界，&#xA;理解它们，才算真正理解了 Go。&lt;/p&gt;&#xA;&lt;h2 id=&#34;它可能的去向&#34;&gt;它可能的去向&lt;/h2&gt;&#xA;&lt;p&gt;Go 的演化仍在继续，而且大多遵循同一条节奏，&lt;strong&gt;先发布最小可用的一版，在真实使用中观察需求，&#xA;再审慎扩充&lt;/strong&gt;。书中已经能看到这条线的延伸：垃圾回收正朝着更好的缓存局部性演进（Green Tea GC，&#xA;&lt;a href=&#34;.././part4memory/ch13gc/greentea&#34;&gt;13.11&lt;/a&gt;）；编译器把优化从静态猜测推向数据驱动（PGO，&#xA;&lt;a href=&#34;.././part5toolchain/ch15compile/optimize&#34;&gt;15.3&lt;/a&gt;）；泛型的运行时开销在被持续打磨；可观测性&#xA;（&lt;a href=&#34;.././part5toolchain/ch16tools/readme&#34;&gt;16&lt;/a&gt;）与结构化诊断仍在补全。可以预见的是，无论加入什么，&#xA;Go 大概率都不会拿编译速度、向后兼容与「读一行代码就能看懂」这几条底线去交换。它的未来，&#xA;多半不是某个特性大爆发的版本，而是这种小步慢走的延续。&lt;/p&gt;&#xA;&lt;h2 id=&#34;笔者结语&#34;&gt;笔者结语&lt;/h2&gt;&#xA;&lt;p&gt;编写本书的难度非常高，这个难度一方面来源于源码自身发展过程带来的复杂性，在编写本书的过程中代码本身也在发展。&#xA;本书最初写于 Go 1.10，为了让保证内容能既能跟上代码进化的速度，又要保证编写的质量，&#xA;则必须在编写的过程中时刻关注那些最基本的、最通用的和最经典而长存的设计。&lt;/p&gt;&#xA;&lt;p&gt;Go 语言源码的研究不仅仅局限于跟用户态代码直接相关的运行时的代码，这只是 Go 源码的一小部分。&#xA;完整理解 Go 的源码不仅需要对各方面系统理论的理解，还需要把控 Go 自身编译器与运行时之间配合，&#xA;这基本上就决定了本书无论是编写起点，还是阅读起点很高；&#xA;这种复杂性还来源于各个标准库与运行时模块之间互相耦合，&#xA;由于 Go 在传统程序流程之上建立了一层运行时机制，进而会导致很多传统意义上的标准库设计变得非常的精巧。&#xA;除非某个标准库或者某个组件是纯粹的不依赖底层实现的逻辑代码，否则很少有内容能够独立成章不与其他章节产生连接，&#xA;也就为线性呈现知识的写作过程提高了难度。&#xA;除了运行时和编译器之外，书中讨论的标准库依然需要笔者在编写之前就需要对各个模块间的耦合关系相当了解，&#xA;同时也要求读者在阅读过程中时刻在脑海中勾勒这些耦合关系，也就进一步提高了阅读本书的门槛。&lt;/p&gt;</description>
    </item>
    <item>
      <title>附录 A：术语表</title>
      <link>/zh-cn/glossary/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/glossary/</guid>
      <description>&lt;h1 id=&#34;术语表&#34;&gt;术语表&lt;/h1&gt;&#xA;&lt;p&gt;本附录整理了书中出现的主要术语，按主题分组、英文字典序排列，并给出术语主要展开的章节，&#xA;便于读者回查。&lt;/p&gt;&#xA;&lt;h2 id=&#34;并发与调度&#34;&gt;并发与调度&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;术语&lt;/th&gt;&#xA;          &lt;th&gt;章节&lt;/th&gt;&#xA;          &lt;th&gt;English&lt;/th&gt;&#xA;          &lt;th&gt;缩写&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;回边&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/preemption&#34;&gt;9.7&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Back Edge&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;协作式抢占&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/preemption&#34;&gt;9.7&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Cooperative Preemption&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;顺序进程通讯&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part1overview/ch01intro/csp&#34;&gt;1.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Communicating Sequential Processes&lt;/td&gt;&#xA;          &lt;td&gt;CSP&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Goroutine&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/mpg&#34;&gt;9.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Goroutine&lt;/td&gt;&#xA;          &lt;td&gt;G/g&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;机器（线程）&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/mpg&#34;&gt;9.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Machine&lt;/td&gt;&#xA;          &lt;td&gt;M/m&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;网络轮询器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/poller&#34;&gt;9.9&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Network Poller&lt;/td&gt;&#xA;          &lt;td&gt;netpoll&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;非自旋&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/schedule&#34;&gt;9.4&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Non-spinning&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;抢占式&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/preemption&#34;&gt;9.7&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Preemptive&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;处理器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/mpg&#34;&gt;9.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Processor&lt;/td&gt;&#xA;          &lt;td&gt;P/p&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;安全点&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/preemption&#34;&gt;9.7&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Safepoint&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;调度器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/readme&#34;&gt;9&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Scheduler&lt;/td&gt;&#xA;          &lt;td&gt;sched&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;自旋&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/schedule&#34;&gt;9.4&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Spinning&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;系统监控&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/sysmon&#34;&gt;9.8&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;System Monitor&lt;/td&gt;&#xA;          &lt;td&gt;sysmon&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;工作窃取&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch09sched/steal&#34;&gt;9.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Work Stealing&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;同步与内存模型&#34;&gt;同步与内存模型&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;术语&lt;/th&gt;&#xA;          &lt;th&gt;章节&lt;/th&gt;&#xA;          &lt;th&gt;English&lt;/th&gt;&#xA;          &lt;th&gt;缩写&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;原子操作&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch11sync/atomic&#34;&gt;11.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Atomic Operation&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;比较并交换&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch11sync/atomic&#34;&gt;11.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Compare-And-Swap&lt;/td&gt;&#xA;          &lt;td&gt;CAS&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;条件变量&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch11sync/cond&#34;&gt;11.4&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Condition Variable&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;数据竞争&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Data Race&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;先行发生&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Happens-Before&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;无锁&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch11sync/atomic&#34;&gt;11.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lock-free&lt;/td&gt;&#xA;          &lt;td&gt;LF&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;内存屏障&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Memory Barrier&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;顺序一致性&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Sequential Consistency&lt;/td&gt;&#xA;          &lt;td&gt;SC&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;假共享&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/component&#34;&gt;12.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;False Sharing&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;真共享&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/component&#34;&gt;12.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;True Sharing&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;无等待&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part3concurrency/ch11sync/atomic&#34;&gt;11.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Wait-free&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;内存分配&#34;&gt;内存分配&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;术语&lt;/th&gt;&#xA;          &lt;th&gt;章节&lt;/th&gt;&#xA;          &lt;th&gt;English&lt;/th&gt;&#xA;          &lt;th&gt;缩写&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;区域&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/init&#34;&gt;12.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Arena&lt;/td&gt;&#xA;          &lt;td&gt;heapArena&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;区域提示&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/init&#34;&gt;12.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Arena Hint&lt;/td&gt;&#xA;          &lt;td&gt;arenaHint&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;快速路径&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/basic&#34;&gt;12.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Fast Path&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;自由表&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/component&#34;&gt;12.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Free List&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;堆&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/readme&#34;&gt;12&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Heap&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;大对象&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/largealloc&#34;&gt;12.4&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Large Object&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;页&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/pagealloc&#34;&gt;12.7&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;页分配器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/pagealloc&#34;&gt;12.7&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Page Allocator&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;大小等级&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/basic&#34;&gt;12.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Size Class&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;小对象&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/smallalloc&#34;&gt;12.5&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Small Object&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;慢速路径&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/basic&#34;&gt;12.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Slow Path&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;微型分配器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/tinyalloc&#34;&gt;12.6&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tiny Allocator&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;微对象&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch12alloc/tinyalloc&#34;&gt;12.6&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tiny Object&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;垃圾回收&#34;&gt;垃圾回收&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;术语&lt;/th&gt;&#xA;          &lt;th&gt;章节&lt;/th&gt;&#xA;          &lt;th&gt;English&lt;/th&gt;&#xA;          &lt;th&gt;缩写&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;位图&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/sweep&#34;&gt;13.5&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Bitmap&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;回收器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/basic&#34;&gt;13.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Collector&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;保守式&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/safe&#34;&gt;13.7&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Conservative&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;终结器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/finalizer&#34;&gt;13.10&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Finalizer&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;垃圾回收&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/readme&#34;&gt;13&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Garbage Collection&lt;/td&gt;&#xA;          &lt;td&gt;GC&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;分代假设&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/generational&#34;&gt;13.8&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Generational Hypothesis&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;混合写屏障&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/barrier&#34;&gt;13.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Hybrid Write Barrier&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;存活性&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/basic&#34;&gt;13.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Liveness&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;标记辅助&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/mark&#34;&gt;13.4&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Mark Assist&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;标记清扫&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/basic&#34;&gt;13.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Mark-Sweep&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;赋值器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/basic&#34;&gt;13.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Mutator&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;调步器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/pacing&#34;&gt;13.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pacer&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;可达性&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/basic&#34;&gt;13.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Reachability&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;记忆集&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/generational&#34;&gt;13.8&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Remembered Set&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;停止一切&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/pacing&#34;&gt;13.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Stop the World&lt;/td&gt;&#xA;          &lt;td&gt;STW&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;三色抽象&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/basic&#34;&gt;13.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Tricolour Abstraction&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;写屏障&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch13gc/barrier&#34;&gt;13.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Write Barrier&lt;/td&gt;&#xA;          &lt;td&gt;WB/wb&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;执行栈&#34;&gt;执行栈&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;术语&lt;/th&gt;&#xA;          &lt;th&gt;章节&lt;/th&gt;&#xA;          &lt;th&gt;English&lt;/th&gt;&#xA;          &lt;th&gt;缩写&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;连续栈&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch14stack/design&#34;&gt;14.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Contiguous Stack&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;函数序言&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part1overview/ch02asm/callconv&#34;&gt;2.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Prologue&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;函数收尾&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch14stack/grow&#34;&gt;14.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Epilogue&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;栈&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch14stack/readme&#34;&gt;14&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Stack&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;栈拷贝&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch14stack/copy&#34;&gt;14.4&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Stack Copy&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;栈增长&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part4memory/ch14stack/grow&#34;&gt;14.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Stack Growth&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;语言特性与编译器&#34;&gt;语言特性与编译器&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;术语&lt;/th&gt;&#xA;          &lt;th&gt;章节&lt;/th&gt;&#xA;          &lt;th&gt;English&lt;/th&gt;&#xA;          &lt;th&gt;缩写&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;调用规范&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part1overview/ch02asm/callconv&#34;&gt;2.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Calling Convention / ABI&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;延迟比特&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part2lang/ch06func/defer&#34;&gt;6.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Defer Bit&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;去虚化&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch15compile/optimize&#34;&gt;15.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Devirtualization&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;逃逸分析&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch15compile/escape&#34;&gt;15.5&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Escape Analysis&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;GC 形状&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part2lang/ch08generics/history&#34;&gt;8.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;GC Shape&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;内联&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch15compile/optimize&#34;&gt;15.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Inlining&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;接口表&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part2lang/ch04type/interface&#34;&gt;4.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Interface Table&lt;/td&gt;&#xA;          &lt;td&gt;itab&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;开放编码式延迟&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part2lang/ch06func/defer&#34;&gt;6.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Open-coded Defer&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;性能制导优化&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch15compile/optimize&#34;&gt;15.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Profile-Guided Optimization&lt;/td&gt;&#xA;          &lt;td&gt;PGO&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;静态单赋值&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch15compile/ssa&#34;&gt;15.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Static Single Assignment&lt;/td&gt;&#xA;          &lt;td&gt;SSA&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;类型集&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part2lang/ch08generics/checker&#34;&gt;8.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Type Set&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;类型描述符&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part2lang/ch04type/type&#34;&gt;4.1&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Type Descriptor&lt;/td&gt;&#xA;          &lt;td&gt;_type&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;模块与工具链&#34;&gt;模块与工具链&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;术语&lt;/th&gt;&#xA;          &lt;th&gt;章节&lt;/th&gt;&#xA;          &lt;th&gt;English&lt;/th&gt;&#xA;          &lt;th&gt;缩写&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;语言服务协议&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch16tools/gopls&#34;&gt;16.7&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Language Server Protocol&lt;/td&gt;&#xA;          &lt;td&gt;LSP&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;最小版本选择&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch17modules/minimum&#34;&gt;17.3&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Minimal Version Selection&lt;/td&gt;&#xA;          &lt;td&gt;MVS&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;竞争检查器&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch16tools/race&#34;&gt;16.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Race Detector&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;语义导入版本&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch17modules/semantics&#34;&gt;17.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Semantic Import Versioning&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;语义化版本&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;.././part5toolchain/ch17modules/semantics&#34;&gt;17.2&lt;/a&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Semantic Versioning&lt;/td&gt;&#xA;          &lt;td&gt;semver&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;</description>
    </item>
    <item>
      <title>资助作者</title>
      <link>/zh-cn/donate/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/donate/</guid>
      <description>&lt;h1 id=&#34;资助作者&#34;&gt;资助作者&lt;/h1&gt;&#xA;&lt;p&gt;感谢你读到这里。本书自始至终免费开放，从第一行字到现在，写作、校对与更新的全部成本都由笔者独自承担。如果这些文字曾在某个深夜帮你看懂了一段晦涩的源码，或是在工作中替你省下了一些摸索的时间，笔者会很高兴。&lt;/p&gt;&#xA;&lt;p&gt;写一本试图覆盖 Go 语言方方面面的书，是一件长期且容易半途而废的事。运行时在演进，工具链在变化，每一次 Go 版本的更新都意味着既有章节需要重新核对与改写。你的资助不会改变本书免费的立场，但它是一种实在的回声：它让笔者知道这件事值得继续做下去，也让后续的修订与扩写有了坚持的理由。&lt;/p&gt;&#xA;&lt;h2 id=&#34;支持方式&#34;&gt;支持方式&lt;/h2&gt;&#xA;&lt;p&gt;如果你愿意给予支持，可以通过下面的微信或支付宝二维码完成。任何金额都不嫌少，心意远比数字重要。&lt;/p&gt;&#xA;&lt;table style=&#34;text-align: center;&#34;&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;微信&lt;/th&gt;&lt;th&gt;支付宝&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&#xA;&lt;img src=&#34;../../assets/wechat.jpg&#34; style=&#34;margin: 0 auto; max-width: 40%&#34;/&gt;&#xA;&lt;/td&gt;&#xA;&lt;td&gt;&#xA;&lt;img src=&#34;../../assets/alipay.jpg&#34; style=&#34;margin: 0 auto; max-width: 40%&#34;&gt;&#xA;&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;若你身处海外，或更习惯使用 PayPal，也可以通过下面的链接进行资助：&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;../../assets/wechat.jpg&#34;&gt;&lt;img src=&#34;https://img.shields.io/badge/%E6%8D%90%E8%B5%A0-%E5%BE%AE%E4%BF%A1-68b600.svg?style=popout-square&amp;amp;logo=WeChat&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&#xA;&lt;a href=&#34;../../assets/alipay.jpg&#34;&gt;&lt;img src=&#34;https://img.shields.io/badge/%E6%8D%90%E8%B5%A0-%E6%94%AF%E4%BB%98%E5%AE%9D-00a2ef.svg?style=popout-square&amp;amp;logo=AliPay&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&#xA;&lt;a href=&#34;https://www.paypal.me/changkunde/4.99eur&#34;&gt;&lt;img src=&#34;https://img.shields.io/badge/%E6%8D%90%E8%B5%A0-PayPal-104098.svg?style=popout-square&amp;amp;logo=PayPal&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
