<?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>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/</link><description>Recent content in 第 11 章 同步原语与模式 on Go 语言原本</description><generator>Hugo</generator><language>zh-cn</language><atom:link href="http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/index.xml" rel="self" type="application/rss+xml"/><item><title>11.1 共享内存式同步模式</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/basic/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/basic/</guid><description>&lt;h1 id="111-共享内存式同步模式"&gt;11.1 共享内存式同步模式&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;程序的构造可以用更简单的基础原语来表达，这一事实是一个有力的保障：这些原语所包含的内容，
能与程序语言的其余部分在逻辑上保持一致。
—— C. A. R. Hoare&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;并发编程的全部困难，可以浓缩成一句话：多个执行单元要在同一份状态上协作，而它们各自推进的
次序无法预知。如何让这种协作既正确又高效，半个多世纪以来分化出两条大的传统。本章讲的是其中
一条，共享内存。在拆解 Go 标准库 &lt;code&gt;sync&lt;/code&gt; 与 &lt;code&gt;sync/atomic&lt;/code&gt; 里那些具体原语之前，本节先把这两条
传统放在一起辨认清楚，说明它们为何是对偶的，再交代 Go 在其间所站的位置。读懂了这层取舍，
后面 &lt;a href=".././mutex"&gt;11.2&lt;/a&gt; 到 &lt;a href=".././mem"&gt;11.9&lt;/a&gt; 各节就不再是孤立的 API，而是同一种设计哲学落到
不同场景上的样子。&lt;/p&gt;
&lt;h2 id="1111-并发的两大传统"&gt;11.1.1 并发的两大传统&lt;/h2&gt;
&lt;p&gt;并发编程历史上有两条大的传统。一条是&lt;strong&gt;共享内存&lt;/strong&gt;：线程共享地址空间，靠互斥锁、信号量、条件
变量等手段协调对共享状态的访问。Dijkstra 1965 年提出的信号量与 Hoare 1974 年提出的监管程
（monitor）是它的奠基石，它也是 C/C++、Java、以及几乎所有操作系统内核的主流路数。另一条是
&lt;strong&gt;消息传递&lt;/strong&gt;：执行单元不共享状态，只通过收发消息协调。Hoare 的 CSP（&lt;a href="../ch10chan"&gt;10.1&lt;/a&gt;）与
Hewitt 的 Actor 模型是它的两支，Erlang、occam 是它的代表。&lt;/p&gt;
&lt;p&gt;两条传统的分野，落在「状态归谁所有」这一点上。共享内存里状态是公有的，谁都能碰，于是必须靠
锁划出临界区，规定同一时刻只许一个执行单元进入；消息传递里状态是私有的，别人想读写只能发一封
信请它代劳，协调由信道的收发时序天然完成。&lt;/p&gt;

&lt;pre class="mermaid"&gt;flowchart LR
 subgraph SM[&amp;#34;共享内存&amp;#34;]
 direction TB
 T1[&amp;#34;线程 1&amp;#34;] --&amp;gt; L[&amp;#34;锁 / 临界区&amp;#34;]
 T2[&amp;#34;线程 2&amp;#34;] --&amp;gt; L
 L --&amp;gt; S[&amp;#34;共享状态&amp;#34;]
 end
 subgraph MP[&amp;#34;消息传递&amp;#34;]
 direction TB
 A1[&amp;#34;执行单元 1&amp;#34;] --&amp;gt;|&amp;#34;消息&amp;#34;| CH[&amp;#34;信道&amp;#34;]
 CH --&amp;gt;|&amp;#34;消息&amp;#34;| A2[&amp;#34;执行单元 2&amp;lt;br/&amp;gt;(私有状态)&amp;#34;]
 end&lt;/pre&gt;
&lt;p&gt;值得一提的是，两者并非非此即彼。Dijkstra 当年用信号量解的「生产者与消费者」「哲学家就餐」
等问题，今天用 channel 同样能写；反过来，channel 的内部实现本身就是一段被互斥锁保护的共享
缓冲（&lt;a href="../ch10chan"&gt;10&lt;/a&gt;）。这种「换一种说法就能从一边翻译到另一边」的观感并非错觉，下一小节
会看到，它早在 1979 年就被证明是一条普遍规律。&lt;/p&gt;</description></item><item><title>11.2 互斥锁</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/mutex/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/mutex/</guid><description>&lt;h1 id="112-互斥锁"&gt;11.2 互斥锁&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;sync.Mutex&lt;/code&gt; 是最朴素的同步原语：同一时刻只让一个 goroutine 进入临界区。朴素的接口之下，它要在
两个互相拉扯的目标间反复权衡，&lt;strong&gt;吞吐&lt;/strong&gt;（让锁尽快被某人拿走，别让 CPU 闲着）与&lt;strong&gt;公平&lt;/strong&gt;（别让某个
倒霉的等待者永远排不到队）。这两者无法同时拉满：把锁严格按到达次序移交，最公平，却要为每次交接
付一次上下文切换；放任刚醒来的与正在跑的自由竞争，最快，却可能让某个等待者一次次落空。这一节
先把互斥这个古老问题的理论与硬件地基铺开，再看 Go 的 mutex 如何在这条钢丝上行走。&lt;/p&gt;
&lt;h2 id="1121-互斥问题与它的硬件地基"&gt;11.2.1 互斥问题与它的硬件地基&lt;/h2&gt;
&lt;p&gt;互斥是并发理论最早的课题之一。Dijkstra 于 1965 年形式化了「互斥问题」，并给出第一个不借助任何
硬件特殊指令、仅凭普通读写就能让多个进程轮流进入临界区的软件解。其后 Lamport 于 1974 年的
&lt;strong&gt;面包店算法&lt;/strong&gt;（bakery algorithm）把这条路走到了一个漂亮的终点：每个想进临界区的线程先「取号」，
再按号码从小到大依次进入，既保证互斥，又保证先到先得的公平排队，而全程只用普通读写，不需要任何
原子读改写指令。&lt;/p&gt;
&lt;p&gt;纯软件解在理论上自足，工程上却昂贵：面包店算法要遍历所有线程的号码，开销随线程数增长，且严重
依赖顺序一致的内存模型（&lt;a href=".././mem"&gt;11.9&lt;/a&gt;），在现代弱序硬件上还得另加屏障。因此现代锁不再走纯软件
这条路，而是直接站在硬件提供的&lt;strong&gt;原子读改写&lt;/strong&gt;指令之上，比较并交换（compare-and-swap, CAS）、
取后加（fetch-and-add）等（&lt;a href=".././atomic"&gt;11.3&lt;/a&gt;）。一条 CAS 就能原子地完成面包店算法要好几步才能
模拟的事，地基一换，上层设计随之改观。&lt;/p&gt;
&lt;p&gt;围绕这些原语，锁长出了一个谱系，理解它有助于看清 Go 的 mutex 站在哪里。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自旋锁&lt;/strong&gt;（spin lock）最简单：用一个 CAS 反复试，抢到就进，抢不到就原地空转再试。它在低竞争
下近乎零成本，但高竞争下是灾难：多个核心争同一个锁变量，每次 CAS 都让那条缓存行在核心之间
来回失效、重取，称为&lt;strong&gt;缓存行弹跳&lt;/strong&gt;（cache-line bouncing），竞争者越多越糟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;票号锁&lt;/strong&gt;（ticket lock）用 fetch-and-add 发号，再让每个线程自旋等自己的号被叫到，由此恢复了
FIFO 公平。但所有等待者仍然自旋在同一个「当前服务号」变量上，缓存行弹跳的老问题并未根除。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCS 锁&lt;/strong&gt;（Mellor-Crummey 与 Scott，1991）是高扩展性锁的经典之作。它让每个等待者把自己挂进
一条&lt;strong&gt;显式队列&lt;/strong&gt;，并各自自旋在&lt;strong&gt;自己的&lt;/strong&gt;本地变量上，前一个持锁者释放时只去写后继者的那个本地
变量。如此一来，无论多少竞争者，每次交接只触动一条缓存行，竞争下的缓存流量降到常数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上都是「忙等」一脉，等待者占着 CPU 空转。另一条线把「睡眠等待」做廉价。早年线程要阻塞只能
陷入内核，即便锁根本没有竞争也要付系统调用的代价。Linux 的 &lt;strong&gt;futex&lt;/strong&gt;（fast userspace mutex，
Franke、Russell 与 Kirkwood，2002）解决了这个痛点：无竞争时加解锁纯在用户态用一次原子操作完成，
只有真要阻塞或唤醒等待者时，才带着那个用户态地址陷入内核去排队。几乎所有现代用户态锁，包括 Go
的 mutex，都踩在 futex（及各平台的等价物）这块「用户态快、内核态兜底」的地基上。Go 自己不直接
调 futex，而是由运行时的信号量（&lt;code&gt;runtime_SemacquireMutex&lt;/code&gt; / &lt;code&gt;runtime_Semrelease&lt;/code&gt;）封装各平台的
底层阻塞原语，对上层呈现统一接口。&lt;/p&gt;</description></item><item><title>11.3 原子操作</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/atomic/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/atomic/</guid><description>&lt;h1 id="113-原子操作"&gt;11.3 原子操作&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;sync/atomic&lt;/code&gt; 是 Go 同步原语里最贴近硬件的一层。互斥锁（&lt;a href=".././mutex"&gt;11.1&lt;/a&gt;）、channel
（&lt;a href="../ch08channel"&gt;8&lt;/a&gt;）这些更高层的工具，内部都建立在原子操作之上。它提供「不可分割」的读写与
读改写：一个原子操作要么完整发生、要么没发生，中间不会被别的 goroutine 看到一半。但原子操作的
意义远不止「防撕裂」。它是无锁编程的地基，而无锁编程背后，有一套关于「哪些原语能做什么」的
深刻理论，那是本节真正想讲清的东西。&lt;/p&gt;
&lt;p&gt;本节先把基本操作摆出来，落到 CAS 这块基石；再退后一步，借 Herlihy 的共识层级回答「为什么是
CAS」；接着进入无锁的暗礁 ABA 与内存回收，说明 Go 为何把硬的无锁结构留在运行时内部；最后回到
工程，讲 &lt;code&gt;sync/atomic&lt;/code&gt; 的顺序一致承诺，以及 Go 1.19 那次把「该用原子访问」编进类型的演进。&lt;/p&gt;
&lt;h2 id="1131-几种基本操作"&gt;11.3.1 几种基本操作&lt;/h2&gt;
&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;（原子交换）、
&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;（原子位运算）。它们都落到
单条带 &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; 的
load-linked / store-conditional 重试对，加法是 &lt;code&gt;LOCK XADD&lt;/code&gt;。换言之，原子性的最终保证来自硬件，
&lt;code&gt;sync/atomic&lt;/code&gt; 只是把这些指令包装成可移植的 Go 函数。&lt;/p&gt;
&lt;p&gt;其中 CAS 是无锁编程的基石。它原子地完成「如果当前值还是我以为的那个，就改成新值，否则什么都
不做并告诉我失败」：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// CompareAndSwap 的语义（伪代码，整体不可分割地执行）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; CompareAndSwapInt64(addr *&lt;span style="color:#2b91af"&gt;int64&lt;/span&gt;, old, new &lt;span style="color:#2b91af"&gt;int64&lt;/span&gt;) (swapped &lt;span style="color:#2b91af"&gt;bool&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;if&lt;/span&gt; *addr == old {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; *addr = new
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;return&lt;/span&gt; &lt;span style="color:#00f"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;return&lt;/span&gt; &lt;span style="color:#00f"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;为什么单单一个 CAS 就够撑起无锁？因为几乎所有无锁算法都能写成同一种骨架，一个&lt;strong&gt;重试循环&lt;/strong&gt;：
读出当前值，据它算出想要的新值，再用 CAS 把它写回去；只有「这期间没人动过」时 CAS 才成功，
否则拿到的就是别人更新后的值，重来一遍即可。&lt;/p&gt;</description></item><item><title>11.4 条件变量</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/cond/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/cond/</guid><description>&lt;h1 id="114-条件变量"&gt;11.4 条件变量&lt;/h1&gt;
&lt;p&gt;互斥锁（&lt;a href=".././mutex"&gt;11.2&lt;/a&gt;）回答的是「谁能进临界区」，条件变量回答的则是另一个问题：
「一个 Goroutine 已经进了临界区，却发现现在还不能干活，它该如何等到能干活的那一刻，
同时把临界区让给别人」。生产者消费者是最常见的例子。队列满时，生产者既不能写入，
又不能一直攥着锁空转，否则消费者永远拿不到锁去腾空间，于是双方一起卡死。条件变量
&lt;code&gt;sync.Cond&lt;/code&gt; 给出的办法是：让生产者「带锁睡去」，原子地交出锁并阻塞，等消费者腾出空间后
把它唤醒，醒来时锁又回到它手上。&lt;/p&gt;
&lt;p&gt;这一节先把用法讲清楚，尤其是那条几乎所有教材都强调、却很少解释清楚来由的规矩：
&lt;code&gt;Wait&lt;/code&gt; 必须写在 &lt;code&gt;for&lt;/code&gt; 循环里。这条规矩不是 Go 的发明，它根植于 1970 年代关于「管程」
（monitor）的两种唤醒语义之争。讲清这段历史，读者就会明白为什么从 pthread 到 Java 再到 Go，
现代条件变量无一例外地要求循环复查。随后我们给出 Go 实现的速写（notifyList 的 ticket 机制、
禁止拷贝的 copyChecker），最后说明一个也许出乎意料的事实：在 Go 里 &lt;code&gt;sync.Cond&lt;/code&gt; 其实很少用，
channel 与 close 广播大多把它取代了。&lt;/p&gt;
&lt;h2 id="1141-用法wait-为何要写在-for-循环里"&gt;11.4.1 用法：Wait 为何要写在 for 循环里&lt;/h2&gt;
&lt;p&gt;先看一段标准的生产者消费者。&lt;code&gt;condition&lt;/code&gt; 是受锁保护的共享状态，&lt;code&gt;cond&lt;/code&gt; 用同一把锁构造：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; main() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	cond := sync.NewCond(new(sync.Mutex))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	condition := 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#008000"&gt;// 消费者&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;go&lt;/span&gt; &lt;span style="color:#00f"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#00f"&gt;for&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			cond.L.Lock()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#00f"&gt;for&lt;/span&gt; condition == 0 { &lt;span style="color:#008000"&gt;// 没有可消费的，带锁睡去&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;				cond.Wait()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			condition--
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			cond.Signal() &lt;span style="color:#008000"&gt;// 腾出了空间，唤醒一个生产者&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			cond.L.Unlock()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#008000"&gt;// 生产者&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;for&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		cond.L.Lock()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#00f"&gt;for&lt;/span&gt; condition == 100 { &lt;span style="color:#008000"&gt;// 队列满了，带锁睡去&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			cond.Wait()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		condition++
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		cond.Signal() &lt;span style="color:#008000"&gt;// 有货了，唤醒一个消费者&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		cond.L.Unlock()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&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;
再返回。「原子地解锁并阻塞」是关键的一步，它堵死了一个否则必然出现的丢失唤醒窗口：
若先解锁、再阻塞分作两步，解锁之后阻塞之前的那一瞬，另一方完全可能改好条件并发出
&lt;code&gt;Signal&lt;/code&gt;，而此刻还没睡着的本方收不到这个信号，待它睡去便再无人来唤，于是永久错过。
&lt;code&gt;Wait&lt;/code&gt; 把解锁与入队做成一体，正是为了消除这个窗口。&lt;/p&gt;</description></item><item><title>11.5 同步组</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/waitgroup/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/waitgroup/</guid><description>&lt;h1 id="115-同步组"&gt;11.5 同步组&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本节内容对标 Go 1.26。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前几节的互斥锁（&lt;a href=".././mutex"&gt;11.2&lt;/a&gt;）与条件变量（&lt;a href=".././cond"&gt;11.4&lt;/a&gt;）解决的是
「多个 Goroutine 争用同一份共享状态」的问题。本节要谈的 &lt;code&gt;sync.WaitGroup&lt;/code&gt; 解决的是另一类
问题：一个 Goroutine 派生出若干子任务，然后等它们全部完成再继续。前者是争用，后者是汇合。
这是并发编程里最常见的一种结构，称为 fork-join，而 &lt;code&gt;WaitGroup&lt;/code&gt; 正是 Go 标准库给它的答案。&lt;/p&gt;
&lt;h2 id="1151-屏障与闩锁的谱系"&gt;11.5.1 屏障与闩锁的谱系&lt;/h2&gt;
&lt;p&gt;在认识 &lt;code&gt;WaitGroup&lt;/code&gt; 的实现之前，先把它放进同步原语的谱系里，看清它解决的是哪一类问题。
等待「一组事件全部发生」这件事，并发文献里有一个专门的家族，叫屏障（barrier）与闩锁（latch）。
家族里的几位成员形态各异，Java 的 &lt;code&gt;java.util.concurrent&lt;/code&gt; 把它们分得相当清楚，正好用来定位
&lt;code&gt;WaitGroup&lt;/code&gt;：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;原语&lt;/th&gt;
 &lt;th&gt;形态&lt;/th&gt;
 &lt;th&gt;可复用&lt;/th&gt;
 &lt;th&gt;计数是否可动态增减&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;CountDownLatch&lt;/code&gt;（Java）&lt;/td&gt;
 &lt;td&gt;一群等待者等计数减到 0，一次性&lt;/td&gt;
 &lt;td&gt;否&lt;/td&gt;
 &lt;td&gt;否，构造时定死&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;CyclicBarrier&lt;/code&gt;（Java）&lt;/td&gt;
 &lt;td&gt;$N$ 个对等方互相等齐，到齐后自动重置&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;td&gt;否，参与方数目固定&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;Phaser&lt;/code&gt;（Java）&lt;/td&gt;
 &lt;td&gt;分阶段屏障，参与方可中途注册 / 注销&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;sync.WaitGroup&lt;/code&gt;（Go）&lt;/td&gt;
 &lt;td&gt;一方等一组任务全部完成，fork-join&lt;/td&gt;
 &lt;td&gt;是（Wait 返回后）&lt;/td&gt;
 &lt;td&gt;是（Add 可正可负）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;WaitGroup&lt;/code&gt; 最接近 &lt;code&gt;CountDownLatch&lt;/code&gt;：都是「计数从某个正值减到 0，等待者随之放行」。区别在两处。
其一，&lt;code&gt;CountDownLatch&lt;/code&gt; 的计数在构造时定死，&lt;code&gt;WaitGroup&lt;/code&gt; 的计数靠 &lt;code&gt;Add&lt;/code&gt; 动态累加，这点更像
&lt;code&gt;Phaser&lt;/code&gt; 的注册机制。其二，&lt;code&gt;CountDownLatch&lt;/code&gt; 是一次性的，减到 0 便报废，而 &lt;code&gt;WaitGroup&lt;/code&gt;
在一轮 &lt;code&gt;Wait&lt;/code&gt; 返回后可以重新使用。&lt;/p&gt;</description></item><item><title>11.6 缓存池</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/pool/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/pool/</guid><description>&lt;h1 id="116-缓存池"&gt;11.6 缓存池&lt;/h1&gt;
&lt;p&gt;频繁地分配又丢弃同一类临时对象，会给垃圾回收器（&lt;a href="../../part4memory/ch13gc"&gt;13&lt;/a&gt;）带来沉重压力。
&lt;code&gt;sync.Pool&lt;/code&gt; 提供一条出路：把用完的对象暂存起来、下次复用，而不是每次都新分配。它的典型用法是
缓冲区、序列化器这类&amp;quot;用完即弃、又反复要用&amp;quot;的临时对象。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;var&lt;/span&gt; bufPool = sync.Pool{New: &lt;span style="color:#00f"&gt;func&lt;/span&gt;() &lt;span style="color:#2b91af"&gt;any&lt;/span&gt; { &lt;span style="color:#00f"&gt;return&lt;/span&gt; new(bytes.Buffer) }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;b := bufPool.Get().(*bytes.Buffer)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;b.Reset() &lt;span style="color:#008000"&gt;// 取回的对象可能是脏的，先重置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// ... 使用 b ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bufPool.Put(b) &lt;span style="color:#008000"&gt;// 用完放回，供他人复用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;New&lt;/code&gt; 是唯一需要用户提供的字段：当池里取不到对象时，由它兜底造一个。于是 &lt;code&gt;Get&lt;/code&gt; 拿到的，
要么是别人刚用完放回的旧对象，要么是 &lt;code&gt;New&lt;/code&gt; 现造的新对象，调用方无从、也不应区分二者。
本节先讲它复用对象的古老思路，再拆开它为并发与 GC 所做的三层设计：每 P 分片、victim 缓存、
以及与运行时 GC 的协同。本节内容对标 go1.26（实现自 Go 1.13 的 victim 缓存方案稳定至今）。&lt;/p&gt;</description></item><item><title>11.7 并发安全散列表</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/map/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/map/</guid><description>&lt;h1 id="117-并发安全散列表"&gt;11.7 并发安全散列表&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本节内容对标 Go 1.26。&lt;code&gt;sync.Map&lt;/code&gt; 在 Go 1.24 经历了一次彻底的内部重写：原先的
read/dirty 双 map 设计被换成一个并发散列字典树（hash-trie）。本节先把这两代设计
各自的来由讲清楚，再把它们放回并发散列表的解法谱系里。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;语言内建的 &lt;code&gt;map&lt;/code&gt; 不是并发安全的。多个 Goroutine 在没有同步的前提下同时读写同一个 &lt;code&gt;map&lt;/code&gt;，
运行时会主动探测到并发访问并以 &lt;code&gt;fatal error: concurrent map read and map write&lt;/code&gt; 终止进程
（&lt;a href="../../../part2lang/ch05data/map"&gt;5.2&lt;/a&gt; 介绍过这个检测机制，它是不可恢复的，&lt;code&gt;recover&lt;/code&gt;
拦不住）。对可用性有要求的服务，这是一处必须正面回答的设计问题：当多个 Goroutine 要共享
一张表，应当用什么结构。&lt;/p&gt;
&lt;p&gt;最朴素的答案是给一张普通 &lt;code&gt;map&lt;/code&gt; 套一把锁：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// 一张普通 map 加一把读写锁，是最直接、也最常够用的并发安全表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;type&lt;/span&gt; RWMutexMap &lt;span style="color:#00f"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	mu sync.RWMutex
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	data &lt;span style="color:#00f"&gt;map&lt;/span&gt;[&lt;span style="color:#2b91af"&gt;any&lt;/span&gt;]&lt;span style="color:#2b91af"&gt;any&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; (m *RWMutexMap) Load(k &lt;span style="color:#2b91af"&gt;any&lt;/span&gt;) (v &lt;span style="color:#2b91af"&gt;any&lt;/span&gt;, ok &lt;span style="color:#2b91af"&gt;bool&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	m.mu.RLock()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	v, ok = m.data[k]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	m.mu.RUnlock()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; (m *RWMutexMap) Store(k, v &lt;span style="color:#2b91af"&gt;any&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	m.mu.Lock()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	m.data[k] = v
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	m.mu.Unlock()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这套写法简单、类型安全（键值类型由你自己定），在多数场景下也足够快。它的瓶颈只在一处：
所有读都要竞争同一把锁。&lt;code&gt;RWMutex&lt;/code&gt; 允许多个读者并发持锁，但持锁本身要更新读者计数，这是一次
对共享缓存行的原子写，核数一多就成为争用点（&lt;a href=".././mutex"&gt;11.2&lt;/a&gt; 讲过这件事）。&lt;code&gt;sync.Map&lt;/code&gt;
要解决的，正是这个「读多到锁本身都嫌贵」的场景。在看它怎么做之前，先把整张解法地图铺开。&lt;/p&gt;</description></item><item><title>11.8 上下文</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/context/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/context/</guid><description>&lt;h1 id="118-上下文"&gt;11.8 上下文&lt;/h1&gt;
&lt;p&gt;一个进入服务器的请求，往往不会只在一个 goroutine 里走完。它会扇出（fan-out）成一棵
goroutine 树：查数据库的、调下游 RPC 的、读缓存的，各自再派生出更小的工作。一旦请求的发起方
不再需要结果，比如客户端断开了连接，或上游已超时，这整棵树上仍在运行的工作就成了纯粹的浪费，
占着连接、锁与内存，却没有人会去读它们的产出。于是问题变成：&lt;strong&gt;怎样把「到此为止」这个信号，
从树根一路传播到每一片叶子，让它们各自停下来。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt; 包就是 Go 对这个问题给出的标准答案。它把取消、截止时间与请求范围内的元数据，
沿着调用链显式传递，使一棵 goroutine 树可以被统一地取消。本节先说清它为什么是协作式而非
强制式的，再沿取消树的结构走一遍实现，最后把它放进结构化并发与跨语言的谱系里来看。&lt;/p&gt;
&lt;h2 id="1181-为什么取消是信号而非强制"&gt;11.8.1 为什么取消是信号，而非强制&lt;/h2&gt;
&lt;p&gt;最朴素的想法，是给 goroutine 一个「杀掉它」的 API。Go 偏偏没有这样的 API，这不是疏漏，
而是从前人的教训里学来的设计。&lt;/p&gt;
&lt;p&gt;Java 早年提供过 &lt;code&gt;Thread.stop()&lt;/code&gt;，能从外部强行终止一个线程。它在 JDK 1.2 就被废弃，原因写在
官方的废弃说明里：被停止的线程会立刻抛出 &lt;code&gt;ThreadDeath&lt;/code&gt;，并&lt;strong&gt;释放它当时持有的所有监视器锁&lt;/strong&gt;。
锁是用来保护一段不变式的，持锁线程被半路打断、锁却被放开，意味着其他线程会看到一个处于
中间状态、不变式被破坏的共享对象，而且毫无征兆。后来的 JDK 干脆让 &lt;code&gt;Thread.stop()&lt;/code&gt;
直接抛 &lt;code&gt;UnsupportedOperationException&lt;/code&gt;，把这扇门彻底关上。强制终止之所以危险，根子在于
外部无从知道目标此刻是否正处在某个不可被打断的临界区里。&lt;/p&gt;
&lt;p&gt;Go 由此选择了相反的路：运行时不提供「杀死一个 goroutine」的手段，取消是一个
&lt;strong&gt;信号&lt;/strong&gt;，由 goroutine 自己在安全的检查点去查看、再自行决定如何收尾。撤销、回滚、释放资源，
这些只有代码自己知道怎么做对，于是就交还给代码自己做。&lt;/p&gt;
&lt;p&gt;在有 &lt;code&gt;context&lt;/code&gt; 之前，这个信号可以用一个 channel 手写出来。关闭一个 channel 会唤醒所有在它上面
等待的接收者（&lt;a href="../../ch10chan/readme"&gt;10.4&lt;/a&gt;），这正是「广播一次取消」的天然载体：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cancel := make(&lt;span style="color:#00f"&gt;chan&lt;/span&gt; &lt;span style="color:#00f"&gt;struct&lt;/span&gt;{})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;go&lt;/span&gt; &lt;span style="color:#00f"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	done := make(&lt;span style="color:#00f"&gt;chan&lt;/span&gt; &lt;span style="color:#00f"&gt;struct&lt;/span&gt;{}, 1)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;go&lt;/span&gt; &lt;span style="color:#00f"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#00f"&gt;defer&lt;/span&gt; &lt;span style="color:#00f"&gt;func&lt;/span&gt;() { done &amp;lt;- &lt;span style="color:#00f"&gt;struct&lt;/span&gt;{}{} }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		do() &lt;span style="color:#008000"&gt;// 执行需要执行的操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;select&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;case&lt;/span&gt; &amp;lt;-cancel:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#008000"&gt;// 提前被取消：等已启动的工作收尾，再撤销其副作用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&amp;lt;-done
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		undo()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;case&lt;/span&gt; &amp;lt;-done:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#008000"&gt;// 顺利结束&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// 出于某些原因希望取消&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;close(cancel)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这段代码已经含着 &lt;code&gt;context&lt;/code&gt; 的全部要点：取消方只负责发信号（&lt;code&gt;close(cancel)&lt;/code&gt;），被取消方在
&lt;code&gt;select&lt;/code&gt; 里把取消信号与正常完成并列等待，收到信号后在自己选定的安全点收尾。&lt;code&gt;context&lt;/code&gt; 做的，
是把这套手写模式抽象成一个接口，并解决一个手写版没有解决的问题：当工作扇出成一棵树时，
如何让一次取消沿树&lt;strong&gt;自动&lt;/strong&gt;传播到每一个后代。&lt;/p&gt;</description></item><item><title>11.9 内存一致模型</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/mem/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/mem/</guid><description>&lt;h1 id="119-内存一致模型"&gt;11.9 内存一致模型&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本节内容对标 Go 1.26。Go 的内存模型在 2022 年随 Go 1.19 做过一次重要修订，
本节在讲清当前模型之后，会专门交代这次修订的来龙去脉（见 &lt;a href="#1198-%E8%AE%BE%E8%AE%A1%E7%9A%84%E6%BC%94%E8%BF%9B"&gt;11.9.8&lt;/a&gt;）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;读者或许注意到，在此前关于 Go 运行时与编译器的讨论中，我们一直回避着一个话题：
Go 的内存模型。回避并非无故。要讲清楚它，需要并发、同步原语乃至硬件层面的铺垫，
而这些铺垫至此方才齐备。作为本章的收尾，亦是全书对 Go 同步原语与同步模式的总结，
我们在此展开这一话题，回答读者心中那个尚未解开的疑问：两个 Goroutine 同时读写同一变量时，
一方的写入在何种条件下才保证为另一方所见。&lt;/p&gt;
&lt;h2 id="1191-内存模型的重要性"&gt;11.9.1 内存模型的重要性&lt;/h2&gt;
&lt;p&gt;内存一致模型，或称内存模型，是一份契约，立于语言用户与语言自身、语言与操作系统平台、
操作系统平台与硬件平台之间。它界定并行之下读写时序得以确定的条件，回答这样一个问题：
某个共享变量是否具备足够的同步，使一个线程的写入能为另一个线程的读取所见。&lt;/p&gt;
&lt;p&gt;一份 Go 程序写成之后，要先后经过编译器的转换与优化、运行其上的操作系统或虚拟机等动态优化器，
以及 CPU 对指令流的优化，方能执行。其中任何一个环节，都可能调整某个变量读写的次序，
使其偏离程序员写下的顺序。没有内存模型作保障，便无从推演程序最终执行时是否正确。&lt;/p&gt;
&lt;p&gt;内存模型的取舍影响深远，关乎程序的可移植性与可维护性。过强的模型压缩硬件与编译器的优化空间，
压低性能上限；一种已选定强模型的硬件体系结构，又难以在不破坏兼容性的前提下转向更弱的模型，
而破坏兼容性的代价，是要求其上的程序重写源码。&lt;/p&gt;
&lt;p&gt;横跨用户、软件与硬件三方，使内存模型的设计格外棘手，至今仍是开放的研究问题。
讨论 Go 的内存模型之前，我们先了解既有的内存模型，以及软硬件之间历次立约的经验与教训。&lt;/p&gt;
&lt;p&gt;为使「顺序被调整」一事具体可见，考虑一段最小的生产者与消费者程序。它们共享 &lt;code&gt;data&lt;/code&gt; 与
&lt;code&gt;done&lt;/code&gt;，一个写入数据并置位标志，另一个等待标志置位后读取数据：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;var&lt;/span&gt; data &lt;span style="color:#2b91af"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;var&lt;/span&gt; done &lt;span style="color:#2b91af"&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; producer() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	data = 42 &lt;span style="color:#008000"&gt;// (1)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	done = &lt;span style="color:#00f"&gt;true&lt;/span&gt; &lt;span style="color:#008000"&gt;// (2)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; consumer() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;for&lt;/span&gt; !done { &lt;span style="color:#008000"&gt;// (3)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	print(data) &lt;span style="color:#008000"&gt;// (4)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;源代码中 (1) 写在 (2) 之前，直觉上 &lt;code&gt;consumer&lt;/code&gt; 一旦读到 &lt;code&gt;done == true&lt;/code&gt;，便应当读到
&lt;code&gt;data == 42&lt;/code&gt;。然而这段程序并不保证打印 42，也可能停在循环中不再退出。前文所说的三道优化，
在此各显其形：编译器可将无依赖的 (1) 与 (2) 互换，或将循环中对 &lt;code&gt;done&lt;/code&gt; 的读取提升至循环外，
使其反复读取同一寄存器中的旧值；CPU 以乱序方式发射指令，只为单个核心维持其自身可见的数据
依赖；写入还会先停留在核心私有的存储缓冲（store buffer）中，何时对其他核心可见，
由缓存一致性协议决定，与写入的先后未必一致。&lt;/p&gt;</description></item><item><title>11.10 进一步阅读的参考文献</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/ref/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch11sync/ref/</guid><description>&lt;p&gt;TODO: atomic.value 的 proposal&lt;/p&gt;
&lt;p&gt;Package context · Golang
Go Concurrency Patterns: Context
Using context cancellation in Go
proposal: context: new package for standard library #14660 &lt;a href="https://github.com/golang/go/issues/14660"&gt;&lt;a href="https://github.com/golang/go/issues/14660"&gt;https://github.com/golang/go/issues/14660&lt;/a&gt;&lt;/a&gt; ↩︎&lt;/p&gt;
&lt;p&gt;Sameer Ajmani. 29 July 2014. “Go Concurrency Patterns: Context” &lt;a href="https://blog.golang.org/context"&gt;&lt;a href="https://blog.golang.org/context"&gt;https://blog.golang.org/context&lt;/a&gt;&lt;/a&gt; ↩︎&lt;/p&gt;
&lt;h2 id="进一步阅读的参考文献"&gt;进一步阅读的参考文献&lt;/h2&gt;
&lt;!-- - [Pike and Cox, 2009] Rob Pike and Russ Cox. The Go Memory Model. February 21, 2009. &lt;a href="https://golang.org/ref/mem"&gt;https://golang.org/ref/mem&lt;/a&gt;
- [Vyukov, 2013] Dmitry Vyukov. cmd/cc: atomic intrinsics. Mar 1, 2013. &lt;a href="https://github.com/golang/go/issues/4947"&gt;https://github.com/golang/go/issues/4947&lt;/a&gt;
- [Cox, 2013] Russ Cox. doc: define how sync/atomic interacts with memory model. Mar 13, 2013. &lt;a href="https://github.com/golang/go/issues/5045"&gt;https://github.com/golang/go/issues/5045&lt;/a&gt;
- [Cox, 2014] Russ Cox. doc: allow buffered channel as semaphore without initialization. March 03, 2014. &lt;a href="https://codereview.appspot.com/75130045"&gt;https://codereview.appspot.com/75130045&lt;/a&gt;
- [Vyukov, 2014a] Dmitry Vyukov. doc: define how sync interacts with memory model. May 7, 2014. &lt;a href="https://github.com/golang/go/issues/7948"&gt;https://github.com/golang/go/issues/7948&lt;/a&gt;
- [Vyukov, 2014b] Dmitry Vyukov. doc: define how finalizers interact with memory model. Dec 25, 2014. &lt;a href="https://github.com/golang/go/issues/9442"&gt;https://github.com/golang/go/issues/9442&lt;/a&gt;
- [Cox, 2016] Russ Cox. Go's Memory Model. February 25, 2016. &lt;a href="http://nil.csail.mit.edu/6.824/2016/notes/gomem.pdf"&gt;http://nil.csail.mit.edu/6.824/2016/notes/gomem.pdf&lt;/a&gt; 
- Fannie Zhang. Specify the memory order guarantee provided by atomic Load/Store. July 15, 2019. &lt;a href="https://groups.google.com/forum/#"&gt;https://groups.google.com/forum/#&lt;/a&gt;!msg/golang-dev/vVkH_9fl1D8/azJa10lkAwAJ --&gt;
&lt;ul&gt;
&lt;li&gt;[Cox, 2013] Russ Cox. gc-aware pool draining policy. Nov 27, 2013. &lt;a href="https://groups.google.com/d/msg/golang-dev/kJ_R6vYVYHU/LjoGriFTYxMJ"&gt;&lt;a href="https://groups.google.com/d/msg/golang-dev/kJ_R6vYVYHU/LjoGriFTYxMJ"&gt;https://groups.google.com/d/msg/golang-dev/kJ_R6vYVYHU/LjoGriFTYxMJ&lt;/a&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Fitzpatrick, 2013] Brad Fitzpatrick. sync: add Pool type. Jan 28, 2013. &lt;a href="https://github.com/golang/go/issues/4720"&gt;&lt;a href="https://github.com/golang/go/issues/4720"&gt;https://github.com/golang/go/issues/4720&lt;/a&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Vyukov, 2014a] Dmitry Vyukov. sync: scalable Pool. Jan 24, 2014. &lt;a href="https://github.com/golang/go/commit/f8e0057bb71cded5bb2d0b09c6292b13c59b5748#diff-2e9fc106a7387ca4c32ecf856a91f82a"&gt;&lt;a href="https://github.com/golang/go/commit/f8e0057bb71cded5bb2d0b09c6292b13c59b5748#diff-2e9fc106a7387ca4c32ecf856a91f82a"&gt;https://github.com/golang/go/commit/f8e0057bb71cded5bb2d0b09c6292b13c59b5748#diff-2e9fc106a7387ca4c32ecf856a91f82a&lt;/a&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Vyukov, 2014b] Dmitry Vyukov. sync: less agressive local caching in Pool. Apr 14, 2014. &lt;a href="https://github.com/golang/go/commit/8fc6ed4c8901d13fe1a5aa176b0ba808e2855af5#diff-2e9fc106a7387ca4c32ecf856a91f82a"&gt;&lt;a href="https://github.com/golang/go/commit/8fc6ed4c8901d13fe1a5aa176b0ba808e2855af5#diff-2e9fc106a7387ca4c32ecf856a91f82a"&gt;https://github.com/golang/go/commit/8fc6ed4c8901d13fe1a5aa176b0ba808e2855af5#diff-2e9fc106a7387ca4c32ecf856a91f82a&lt;/a&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Vyukov, 2014c] sync: release Pool memory during second and later GCs. Oct 22, 2014. &lt;a href="https://github.com/golang/go/commit/af3868f1879c7f8bef1a4ac43cfe1ab1304ad6a4#diff-491b0013c82345bf6cfa937bd78b690d"&gt;&lt;a href="https://github.com/golang/go/commit/af3868f1879c7f8bef1a4ac43cfe1ab1304ad6a4#diff-491b0013c82345bf6cfa937bd78b690d"&gt;https://github.com/golang/go/commit/af3868f1879c7f8bef1a4ac43cfe1ab1304ad6a4#diff-491b0013c82345bf6cfa937bd78b690d&lt;/a&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Clements, 2019a] Austin Clements. sync: use lock-free structure for Pool stealing. Mar 1, 2019. &lt;a href="https://github.com/golang/go/commit/d5fd2dd6a17a816b7dfd99d4df70a85f1bf0de31"&gt;&lt;a href="https://github.com/golang/go/commit/d5fd2dd6a17a816b7dfd99d4df70a85f1bf0de31"&gt;https://github.com/golang/go/commit/d5fd2dd6a17a816b7dfd99d4df70a85f1bf0de31&lt;/a&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Clements, 2019b] Austin Clements. sync: smooth out Pool behavior over GC with a victim cache. Mar 2, 2019. &lt;a href="https://github.com/golang/go/commit/2dcbf8b3691e72d1b04e9376488cef3b6f93b286"&gt;&lt;a href="https://github.com/golang/go/commit/2dcbf8b3691e72d1b04e9376488cef3b6f93b286"&gt;https://github.com/golang/go/commit/2dcbf8b3691e72d1b04e9376488cef3b6f93b286&lt;/a&gt;&lt;/a&gt;)
&lt;a href="https://faiface.github.io/post/context-should-go-away-go2/"&gt;&lt;a href="https://faiface.github.io/post/context-should-go-away-go2/"&gt;https://faiface.github.io/post/context-should-go-away-go2/&lt;/a&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>