<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>第 11 章 同步原语与模式 on Go 语言原本</title>
    <link>/zh-cn/part3concurrency/ch11sync/</link>
    <description>Recent content in 第 11 章 同步原语与模式 on Go 语言原本</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <atom:link href="/zh-cn/part3concurrency/ch11sync/index.xml" rel="self" type="application/rss+xml" />
    <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>
  </channel>
</rss>
