<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>第 10 章 通道与 select on Go 语言原本</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/</link><description>Recent content in 第 10 章 通道与 select on Go 语言原本</description><generator>Hugo</generator><language>zh-cn</language><atom:link href="http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/index.xml" rel="self" type="application/rss+xml"/><item><title>10.1 通道与 CSP 的工程化</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/model/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/model/</guid><description>&lt;h1 id="101-通道与-csp-的工程化"&gt;10.1 通道与 CSP 的工程化&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本节内容提供一个线上演讲：&lt;a href="https://www.youtube.com/watch?v=d7fFCGGn0Wc"&gt;YouTube 在线&lt;/a&gt;，
&lt;a href="https://changkun.de/s/chansrc/"&gt;Google Slides 讲稿&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CSP 给了 Go 一个主张：进程之间不共享状态，只靠传递消息来协调（&lt;a href="../../../part1overview/ch01intro/csp"&gt;1.3&lt;/a&gt;）。
这一节关心的是另一个问题：一门工程语言要把这个主张落地，需要把「通信」做成什么样子，
才能让普通程序员顺手用对。Go 的答案是 channel，一个一等的同步兼通信原语。本节先把它在
语言表层的模型讲清楚，它的类型、收发语法、有无缓冲的两种语义、方向限定与 nil 行为，
为后续各节深入运行时实现（&lt;a href=".././impl"&gt;10.2&lt;/a&gt;–&lt;a href=".././pattern"&gt;10.7&lt;/a&gt;）铺好直觉。&lt;/p&gt;
&lt;h2 id="1011-把通信做成一等原语"&gt;10.1.1 把通信做成一等原语&lt;/h2&gt;
&lt;p&gt;「不要以共享内存的方式通信，而要以通信的方式共享内存。」这句格言常被引用，但它在 Go 里不是
一句口号，而是由 channel 这个具体的语言构造兑现的。CSP 的渊源、它与 1978 年原始论文的异同，
已在 &lt;a href="../../../part1overview/ch01intro/csp"&gt;1.3&lt;/a&gt; 交代，这里不再重复，只接着说 Go 在工程上做了
什么取舍。&lt;/p&gt;
&lt;p&gt;把 CSP 的「通信」搬进一门通用语言，至少要回答三件事：通信经由什么载体、这个载体能否像普通值
一样使用、它如何与类型系统咬合。Go 给出的载体是 channel，并且把它做成了&lt;strong&gt;一等&lt;/strong&gt;（first-class）
的：channel 是有类型的值，可以由 &lt;code&gt;make&lt;/code&gt; 创建、用变量持有、作为参数传给函数、作为返回值传出、
存进结构体字段、放进 slice 或 map。这一点与 Hoare 原始 CSP 按进程名直接通信的设计不同
（&lt;a href="../../../part1overview/ch01intro/csp"&gt;1.3.2&lt;/a&gt;），也正是 Go 把抽象主张变为可组合工具的关键:
既然 channel 是值，「把一个通信端口交给另一段代码」就退化成了普通的传参，无需任何特殊机制。&lt;/p&gt;
&lt;p&gt;channel 同时承担两件常被分开的事:&lt;strong&gt;同步&lt;/strong&gt;与&lt;strong&gt;数据传递&lt;/strong&gt;。一次收发既搬运了一个值，又在收发
双方之间建立了一个发生序（happens-before）关系，后者保证了「发送方在发送前写下的内存，接收方
在接收后一定看得见」。换句话说，channel 不仅是一根传值的管子，它还是同步原语。这条发生序
保证是 channel 能替代显式加锁的根基，其精确条文留待 &lt;a href=".././lockfree"&gt;10.6&lt;/a&gt; 与
&lt;a href="../../ch11sync/mem"&gt;11.9&lt;/a&gt; 展开，本节只需记住:&lt;strong&gt;收发自带同步&lt;/strong&gt;。&lt;/p&gt;</description></item><item><title>10.2 hchan：通道的内部结构</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/impl/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/impl/</guid><description>&lt;h1 id="102-hchan通道的内部结构"&gt;10.2 hchan：通道的内部结构&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././readme"&gt;10.1&lt;/a&gt; 从 CSP 的角度交代了通道在语言里的位置：它是显式的消息信道，
把「通信」与「同步」合二为一。这一节把它拆开来看。一个 channel 在运行时里就是一个名为 &lt;code&gt;hchan&lt;/code&gt;
的结构体，它的全部秘密不过是一把锁、一圈环形缓存、外加两条等待队列。结构虽小，每个字段的存在
都对应一处设计上的考量，读懂这几样东西，后面收发与 select 的全部逻辑（&lt;a href=".././send"&gt;10.3&lt;/a&gt;–
&lt;a href=".././lock"&gt;10.6&lt;/a&gt;）就只是「在这张图上搬运数据、挂起与唤醒 goroutine」。&lt;/p&gt;
&lt;p&gt;沿用本书讲运行时数据结构的惯例，下文给出的结构体是&lt;strong&gt;裁剪后的速写&lt;/strong&gt;，只留与设计相关的字段，
并在注释里说明它为何存在。完整定义可对照 &lt;code&gt;runtime/chan.go&lt;/code&gt; 与 &lt;code&gt;runtime/runtime2.go&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="1021-三条不变式先看为什么"&gt;10.2.1 三条不变式：先看「为什么」&lt;/h2&gt;
&lt;p&gt;在逐字段拆解之前，先抄下 &lt;code&gt;chan.go&lt;/code&gt; 开头那段注释立下的三条不变式，它们是整套结构的设计意图，
比任何字段说明都更能说清「为何如此」：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;c.sendq&lt;/code&gt; 与 &lt;code&gt;c.recvq&lt;/code&gt; 至少有一个为空（唯一的例外是：一个无缓冲 channel 上，某个
goroutine 用 select 同时在收和发两侧挂着）；&lt;/li&gt;
&lt;li&gt;对缓冲 channel，&lt;code&gt;c.qcount &amp;gt; 0&lt;/code&gt; 蕴含 &lt;code&gt;c.recvq&lt;/code&gt; 为空；&lt;/li&gt;
&lt;li&gt;对缓冲 channel，&lt;code&gt;c.qcount &amp;lt; c.dataqsiz&lt;/code&gt; 蕴含 &lt;code&gt;c.sendq&lt;/code&gt; 为空。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这三条用日常语言说就是一句话：&lt;strong&gt;缓存里还有货，就不该有接收方在干等；缓存还有空位，就不该有
发送方在干等。&lt;/strong&gt; 一旦缓存与「干等的人」同时存在，说明本可以直接成交却没成交，那是 bug。整个
收发实现（&lt;a href=".././send"&gt;10.3&lt;/a&gt;、&lt;a href=".././recv"&gt;10.4&lt;/a&gt;）所做的，正是时刻维持这三条：能直接成交就
直接成交，实在成交不了才把自己挂进等待队列。带着这个意图回看下面的字段，就不再是枯燥的清单。&lt;/p&gt;
&lt;h2 id="1022-hchan-的速写"&gt;10.2.2 hchan 的速写&lt;/h2&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;/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;// hchan：一个 channel 的运行时表示（速写）&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; hchan &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; qcount &lt;span style="color:#2b91af"&gt;uint&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; dataqsiz &lt;span style="color:#2b91af"&gt;uint&lt;/span&gt; &lt;span style="color:#008000"&gt;// 环形缓存的容量（make 的第二个参数）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; buf unsafe.Pointer &lt;span style="color:#008000"&gt;// 指向一段可存 dataqsiz 个元素的连续数组&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; elemsize &lt;span style="color:#2b91af"&gt;uint16&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; closed &lt;span style="color:#2b91af"&gt;uint32&lt;/span&gt; &lt;span style="color:#008000"&gt;// 是否已 close&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; elemtype *_type &lt;span style="color:#008000"&gt;// 元素类型：拷贝元素、写屏障、给 buf 标注 GC 信息都要用它&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sendx &lt;span style="color:#2b91af"&gt;uint&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; recvx &lt;span style="color:#2b91af"&gt;uint&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; recvq waitq &lt;span style="color:#008000"&gt;// 阻塞的接收方队列（ &amp;lt;-ch ）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sendq waitq &lt;span style="color:#008000"&gt;// 阻塞的发送方队列（ ch&amp;lt;- ）&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; lock mutex &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;/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;qcount&lt;/code&gt;、&lt;code&gt;dataqsiz&lt;/code&gt;、&lt;code&gt;buf&lt;/code&gt;、&lt;code&gt;sendx&lt;/code&gt;、&lt;code&gt;recvx&lt;/code&gt; 五个合起来描述那圈&lt;strong&gt;环形缓存&lt;/strong&gt;
（10.2.3）；&lt;code&gt;recvq&lt;/code&gt;、&lt;code&gt;sendq&lt;/code&gt; 是两条&lt;strong&gt;等待队列&lt;/strong&gt;（10.2.4）；&lt;code&gt;closed&lt;/code&gt; 记录关闭状态，
&lt;code&gt;elemsize&lt;/code&gt; 与 &lt;code&gt;elemtype&lt;/code&gt; 是为搬运元素备下的元信息。其中 &lt;code&gt;dataqsiz&lt;/code&gt; 在创建之后再不写入，
因此运行时可以无锁地读它来快速判断 channel 是否有缓存（&lt;code&gt;full&lt;/code&gt; 等辅助函数依赖这一点）。
至于 &lt;code&gt;elemtype&lt;/code&gt;，它不只用于按值拷贝元素和触发写屏障，还在创建时被交给分配器，
用来给 &lt;code&gt;buf&lt;/code&gt; 标注「这段内存里哪些位置是指针」，这一点到 10.2.5 会再回来。
（go1.26 的 &lt;code&gt;hchan&lt;/code&gt; 还有 &lt;code&gt;timer&lt;/code&gt; 与 &lt;code&gt;bubble&lt;/code&gt; 两个字段，分别服务于 &lt;code&gt;time&lt;/code&gt; 定时器与
&lt;code&gt;testing/synctest&lt;/code&gt;，与本节的主线无关，速写中略去。）&lt;/p&gt;</description></item><item><title>10.3 收发与直接传递</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/sendrecv/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/sendrecv/</guid><description>&lt;h1 id="103-收发与直接传递"&gt;10.3 收发与直接传递&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本节内容对标 Go 1.26。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=".././impl"&gt;10.2&lt;/a&gt; 把 &lt;code&gt;hchan&lt;/code&gt; 这副骨架摆了出来：一把锁、一个环形缓存 &lt;code&gt;buf&lt;/code&gt;，外加发送方队列
&lt;code&gt;sendq&lt;/code&gt; 与接收方队列 &lt;code&gt;recvq&lt;/code&gt;。这一节让骨架动起来，回答一次 &lt;code&gt;ch &amp;lt;- v&lt;/code&gt; 与 &lt;code&gt;v := &amp;lt;-ch&lt;/code&gt; 在运行时
里究竟发生了什么。读懂这条收发路径，channel 的两条最常被追问的性质,无缓冲 channel 为何是
一次「会合」（rendezvous）、为何对它而言接收发生在发送完成之前（&lt;a href="../../ch11sync/mem"&gt;11.9&lt;/a&gt;），
都会落到同一处机制上：&lt;strong&gt;直接传递&lt;/strong&gt;（direct send / receive）。&lt;/p&gt;
&lt;p&gt;收发的设计要同时满足三个约束。其一，正确：不能丢数据，不能让已关闭的 channel 吞下新值。
其二，快：无竞争时一次收发应当只是「拿锁、拷一次、放锁」，热路径上连锁都尽量不碰。其三，
公平：多个发送方或接收方阻塞在同一个 channel 上时，唤醒次序应当可预期（&lt;a href="#1035-fifo-%E5%85%AC%E5%B9%B3%E4%B8%8E%E4%B8%80%E5%A4%84%E5%8E%86%E5%8F%B2%E6%95%99%E8%AE%AD"&gt;10.3.5&lt;/a&gt;）。
下文先走通发送，再以对称的笔法带过接收，最后落到那处把二者统一起来的优化。&lt;/p&gt;
&lt;h2 id="1031-chansend-的三岔决策"&gt;10.3.1 chansend 的三岔决策&lt;/h2&gt;
&lt;p&gt;编译器把 &lt;code&gt;ch &amp;lt;- v&lt;/code&gt; 译成 &lt;code&gt;chansend1&lt;/code&gt;，后者转调更通用的 &lt;code&gt;chansend&lt;/code&gt;。&lt;code&gt;chansend&lt;/code&gt; 的第三个参数
&lt;code&gt;block&lt;/code&gt; 区分阻塞收发与 &lt;code&gt;select&lt;/code&gt; 里的非阻塞分支（&lt;a href=".././select"&gt;10.5&lt;/a&gt;）。剥去竞态检测、&lt;code&gt;synctest&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;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;29
&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;30
&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;31
&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;32
&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;33
&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;34
&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;35
&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;36
&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;37
&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;38
&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;39
&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;40
&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;// chansend：向 channel 发送 ep 指向的值（裁剪后的速写）&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; chansend(c *hchan, ep unsafe.Pointer, block &lt;span style="color:#2b91af"&gt;bool&lt;/span&gt;) &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; c == &lt;span style="color:#00f"&gt;nil&lt;/span&gt; { &lt;span style="color:#008000"&gt;// 向 nil channel 发送：永久阻塞&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; !block { &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; gopark(&lt;span style="color:#00f"&gt;nil&lt;/span&gt;, &lt;span style="color:#00f"&gt;nil&lt;/span&gt;, waitReasonChanSendNilChan, ...) &lt;span style="color:#008000"&gt;// 不再返回&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; throw(&lt;span style="color:#a31515"&gt;&amp;#34;unreachable&amp;#34;&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;// 非阻塞快路径：不加锁就能判定的失败（详见 10.3.4）&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; !block &amp;amp;&amp;amp; c.closed == 0 &amp;amp;&amp;amp; full(c) {
&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;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lock(&amp;amp;c.lock)
&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;if&lt;/span&gt; c.closed != 0 { &lt;span style="color:#008000"&gt;// 向已关闭 channel 发送：panic&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; unlock(&amp;amp;c.lock)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; panic(plainError(&lt;span style="color:#a31515"&gt;&amp;#34;send on closed channel&amp;#34;&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;// 岔路一：recvq 里有等待的接收方 -&amp;gt; 直接传递，绕过 buf&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; sg := c.recvq.dequeue(); sg != &lt;span style="color:#00f"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; send(c, sg, ep, &lt;span style="color:#00f"&gt;func&lt;/span&gt;() { unlock(&amp;amp;c.lock) })
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000"&gt;// 岔路二：缓存还有空位 -&amp;gt; 把值拷进环形 buf&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; c.qcount &amp;lt; c.dataqsiz {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; qp := chanbuf(c, c.sendx)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; typedmemmove(c.elemtype, qp, ep) &lt;span style="color:#008000"&gt;// 拷入 buf 的 sendx 槽&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; c.sendx++
&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; c.sendx == c.dataqsiz { c.sendx = 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; c.qcount++
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; unlock(&amp;amp;c.lock)
&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000"&gt;// 岔路三：无接收方、buf 也满 -&amp;gt; 把自己挂进 sendq 并 park&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#008000"&gt;// ……见 10.3.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;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;三岔的优先级本身就是设计：&lt;strong&gt;有等待的接收方时，永远优先直接交给它&lt;/strong&gt;，哪怕这是个带缓存且缓存
里还有空位的 channel。直觉上似乎该「先填缓存」，但只要 &lt;code&gt;recvq&lt;/code&gt; 非空，就说明缓存此刻必然为空
（否则接收方早从缓存取走了，不会阻塞），于是绕过缓存直接交付不仅合法，还省下一次拷贝。
这处优先级是下一节那个优化的前提。&lt;/p&gt;</description></item><item><title>10.4 关闭的语义</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/close/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/close/</guid><description>&lt;h1 id="104-关闭的语义"&gt;10.4 关闭的语义&lt;/h1&gt;
&lt;p&gt;前几节里，发送与接收都是「一对一」的会合：一次发送对上一次接收，多余的一方便阻塞等待。
关闭是 channel 上唯一的「一对多」操作。&lt;code&gt;close(ch)&lt;/code&gt; 由一个 Goroutine 发出，却能在一瞬间
唤醒所有正阻塞在这条 channel 上的接收方，并让所有阻塞的发送方就地 panic。这种「一次广播、
众人皆醒」的能力，使关闭从一个看似不起眼的清理动作，变成了 Go 并发里最常用的取消与收尾
机制。done channel 模式、&lt;code&gt;context&lt;/code&gt; 的取消（&lt;a href="../../../part3concurrency/ch11sync/context"&gt;11.8&lt;/a&gt;），
根子都在这里。&lt;/p&gt;
&lt;p&gt;这一节回答三个问题：&lt;code&gt;close&lt;/code&gt; 在运行时究竟做了什么，为什么它能成为广播原语；从一条已关闭的
channel 上接收会读到什么，&lt;code&gt;for range&lt;/code&gt; 因何而终止；以及语言为关闭划下的三条 panic 红线，
为什么它们宁可让程序当场崩溃，也不肯悄悄容错。&lt;/p&gt;
&lt;h2 id="1041-close-做了什么一次加锁广播"&gt;10.4.1 close 做了什么：一次加锁广播&lt;/h2&gt;
&lt;p&gt;关闭的实现集中在 &lt;code&gt;runtime.closechan&lt;/code&gt; 一个函数里。它的骨架可以裁剪成三段:先在锁内置位
&lt;code&gt;closed&lt;/code&gt;，再把 &lt;code&gt;recvq&lt;/code&gt; 与 &lt;code&gt;sendq&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;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;29
&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;30
&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;31
&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;32
&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;33
&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;34
&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;35
&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;36
&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;37
&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;38
&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;39
&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;40
&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;41
&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;42
&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;43
&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;44
&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;45
&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;46
&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;47
&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;48
&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;49
&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;50
&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;51
&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;// closechan：关闭一条 channel（速写，去掉了竞态检测与 synctest 分支）&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; closechan(c *hchan) {
&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; c == &lt;span style="color:#00f"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; panic(plainError(&lt;span style="color:#a31515"&gt;&amp;#34;close of nil channel&amp;#34;&lt;/span&gt;)) &lt;span style="color:#008000"&gt;// 红线一：close(nil)&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; lock(&amp;amp;c.lock)
&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; c.closed != 0 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; unlock(&amp;amp;c.lock)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; panic(plainError(&lt;span style="color:#a31515"&gt;&amp;#34;close of closed channel&amp;#34;&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; c.closed = 1 &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;var&lt;/span&gt; glist gList &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:#008000"&gt;// 唤醒所有接收者：每个都将拿到零值，ok == false&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; sg := c.recvq.dequeue()
&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; sg == &lt;span style="color:#00f"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;break&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;if&lt;/span&gt; sg.elem != &lt;span style="color:#00f"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; typedmemclr(c.elemtype, sg.elem) &lt;span style="color:#008000"&gt;// 把接收方的目标内存清零，即「零值」&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sg.elem = &lt;span style="color:#00f"&gt;nil&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; gp := sg.g
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gp.param = unsafe.Pointer(sg)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sg.success = &lt;span style="color:#00f"&gt;false&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; glist.push(gp)
&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;// 唤醒所有发送者：它们醒来后会 panic&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; sg := c.sendq.dequeue()
&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; sg == &lt;span style="color:#00f"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#00f"&gt;break&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; sg.elem = &lt;span style="color:#00f"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gp := sg.g
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gp.param = unsafe.Pointer(sg)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sg.success = &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; glist.push(gp)
&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; unlock(&amp;amp;c.lock)
&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; !glist.empty() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gp := glist.pop()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; goready(gp, 3)
&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;/p&gt;</description></item><item><title>10.5 select 的实现</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/select/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/select/</guid><description>&lt;h1 id="105-select-的实现"&gt;10.5 select 的实现&lt;/h1&gt;
&lt;p&gt;前几节把单个 channel 的收发（&lt;a href=".././sendrecv"&gt;10.3&lt;/a&gt;）讲清楚了。可现实里的
Goroutine 往往不只盯着一条 channel：它要在多个收发操作中，对&lt;strong&gt;先就绪的那个&lt;/strong&gt;作出反应，
还要能在「一个都没就绪」时不被阻塞。&lt;code&gt;select&lt;/code&gt; 正是为此而生。它的语义看似简单，落到实现上却要同时
解决两个棘手问题：多个就绪分支时如何&lt;strong&gt;公平&lt;/strong&gt;地选一个，以及为执行一次 select 而锁住多条 channel
时如何&lt;strong&gt;不死锁&lt;/strong&gt;。这两点决定了 &lt;code&gt;selectgo&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;/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;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; v := &amp;lt;-ch1: &lt;span style="color:#008000"&gt;// ch1 可接收时执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; use(v)
&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; ch2 &amp;lt;- x: &lt;span style="color:#008000"&gt;// ch2 可发送时执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sent()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;default&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; nonblocking()
&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;约定先摆在前面。每个 case 描述一个 channel 操作（收或发），&lt;code&gt;select&lt;/code&gt; 求值后&lt;strong&gt;至多执行一个&lt;/strong&gt;
case 的通信。若有多个 case 同时就绪，从中&lt;strong&gt;等概率随机&lt;/strong&gt;挑一个；若都未就绪，有 &lt;code&gt;default&lt;/code&gt; 则执行
&lt;code&gt;default&lt;/code&gt;（这使整条 select 变成非阻塞），无 &lt;code&gt;default&lt;/code&gt; 则阻塞，直到某个 case 就绪。语言规范
（&lt;a href="https://go.dev/ref/spec#Select_statements"&gt;Select statements&lt;/a&gt;）把这套语义钉死，运行时
负责兑现，编译器负责把 source 形态翻译成运行时能消化的数据。&lt;/p&gt;</description></item><item><title>10.6 内存模型与无锁演进</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/lockfree/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/lockfree/</guid><description>&lt;h1 id="106-内存模型与无锁演进"&gt;10.6 内存模型与无锁演进&lt;/h1&gt;
&lt;p&gt;前几节把 channel 拆到了零件：&lt;a href=".././send"&gt;10.3&lt;/a&gt; 的直接交付（direct handoff）让收发两端
绕过环形缓冲直接传值，&lt;a href=".././select"&gt;10.5&lt;/a&gt; 的 &lt;code&gt;select&lt;/code&gt; 在多个就绪分支间做随机选择。这些机制
解释了 channel「怎么跑」。本节回答两个更上层的问题：channel 给并发程序提供了&lt;strong&gt;什么可见性
承诺&lt;/strong&gt;，以及一个常被追问的工程疑案，channel 为什么至今仍是「一把锁加一个队列」，而不是
传说中更快的无锁结构。前者把 channel 接回 &lt;a href="../../ch11sync/mem"&gt;11.9&lt;/a&gt; 的内存模型，后者则是
一则关于「正确性与可维护性如何压过峰值性能」的真实取舍。&lt;/p&gt;
&lt;h2 id="1061-channel-的内存模型承诺"&gt;10.6.1 channel 的内存模型承诺&lt;/h2&gt;
&lt;p&gt;channel 不只是传数据的管子，它同时是建立 happens-before 的同步点。&lt;a href="../../ch11sync/mem"&gt;11.9&lt;/a&gt;
给出了 Go 内存模型的全貌，这里把其中与 channel 相关的四条规则单独拎出，逐条读懂它们的含义。
Go 内存模型（go.dev/ref/mem，2022 年 6 月版）对 channel 的规定如下，我们沿用 1.19 修订后的
术语 &lt;em&gt;synchronized before&lt;/em&gt;（同步先于，记作 $&lt;$）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一次发送&lt;strong&gt;同步先于&lt;/strong&gt;对应接收的完成。对任意 channel（无论是否带缓冲）均成立。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;close(ch)&lt;/code&gt; &lt;strong&gt;同步先于&lt;/strong&gt;一个因 channel 关闭而返回零值的接收。&lt;/li&gt;
&lt;li&gt;对&lt;strong&gt;无缓冲&lt;/strong&gt; channel，一次接收&lt;strong&gt;同步先于&lt;/strong&gt;对应发送的完成。&lt;/li&gt;
&lt;li&gt;对容量为 $C$ 的缓冲 channel，第 $k$ 次接收&lt;strong&gt;同步先于&lt;/strong&gt;第 $k+C$ 次发送的完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第 1 条是最朴素的直觉：把数据放进 channel 之前的所有写入，接收方在拿到这份数据之后都能看见。
这正是「不要用共享内存来通信，而要用通信来共享内存」这句格言的形式化基础。一段最小的例子：&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;/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 = 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;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;	close(done) &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;	&amp;lt;-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;	print(data) &lt;span style="color:#008000"&gt;// (4) 保证读到 42&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) sequenced before (2)，由规则 2，(2) synchronized before (3)，(3) sequenced before (4)，
三段拼接的传递闭包给出 (1) $&lt;$ (4)，于是 (4) 必然读到 42。把这条链画出来，跨 goroutine 的那
一步（&lt;code&gt;close&lt;/code&gt; 同步先于接收）正是把两条程序序粘起来的关键边：&lt;/p&gt;</description></item><item><title>10.7 工程实践与跨语言对照</title><link>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/pattern/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part3concurrency/ch10chan/pattern/</guid><description>&lt;h1 id="107-工程实践与跨语言对照"&gt;10.7 工程实践与跨语言对照&lt;/h1&gt;
&lt;p&gt;前面几节把 channel 的内部结构、收发路径与 select 的实现讲透了。读懂了机制，
随之而来的是一个更难、也更工程的问题：什么时候该用 channel，什么时候不该用。
Go 的宣传语「Do not communicate by sharing memory; instead, share memory by communicating」
容易被读成「凡共享状态皆应走 channel」，但这并非作者的本意。这一节把这句话还原成一条
可操作的判别规则，对照同步原语章（&lt;a href="../../ch11sync/readme"&gt;第 11 章&lt;/a&gt;）里更轻的工具，
再把 Go 的选择放进 CSP 家族的谱系里看，理解它在设计空间中的具体坐标。&lt;/p&gt;
&lt;h2 id="1071-channel-不是万能锤"&gt;10.7.1 channel 不是万能锤&lt;/h2&gt;
&lt;p&gt;2018 年 GopherCon 上，Go 团队的 Bryan C. Mills 做了一场题为「Rethinking Classical
Concurrency Patterns」的演讲。他逐个检视了教科书里常见的并发范式，结论是：其中相当一部分
若用 channel 实现，反而比用 &lt;code&gt;sync&lt;/code&gt; 包里的原语更难写对、更慢。&lt;/p&gt;
&lt;p&gt;第一个例子是「用 channel 模拟条件变量」。一个常见的写法是用一个带缓冲的 channel 当作
「信号槽」，发送代表通知、接收代表等待：&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;/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;// 反例：用 channel 当条件变量，看似优雅，边界全是坑&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; Cond &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; ch &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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; (c *Cond) Wait() { &amp;lt;-c.ch }
&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; (c *Cond) Signal() { c.ch &amp;lt;- &lt;span style="color:#00f"&gt;struct&lt;/span&gt;{}{} } &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;Signal&lt;/code&gt; 在无人等待时会阻塞或丢失信号，&lt;code&gt;Broadcast&lt;/code&gt;（唤醒全部等待者）无法表达，
而条件谓词的重新检查（被唤醒后必须重新判断条件是否真的满足）也没有着落。
正确的工具是 &lt;code&gt;sync.Cond&lt;/code&gt;（&lt;a href="../../ch11sync/cond"&gt;11.4&lt;/a&gt;），它的 &lt;code&gt;Wait&lt;/code&gt; 在唤醒后让调用者
回到循环里复查谓词，&lt;code&gt;Broadcast&lt;/code&gt; 一次唤醒所有等待者，这些语义 channel 都不天然具备。&lt;/p&gt;</description></item></channel></rss>