golang 标准库源码解析 - channel
<blockquote>
<p>本文源码版本基于 go1.21.13</p>
</blockquote>
<h1 id="channel"><a class="anchor" href="#channel">#</a> channel</h1>
<p>Golang 中使用 CSP 中 channel 的概念。channel 是被单独创建并且可以在进程之间传递,它的通信模式类似于 boss-worker 模式,一个实体通过将消息发送到 channel 中,然后由监听这个 channel 的实体处理,两个实体之间是没有互相感知的,这个就实现实体之间的解耦。</p>
<h2 id="chan结构"><a class="anchor" href="#chan结构">#</a> chan 结构</h2>
<figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">type</span> hchan <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> qcount <span class="token builtin">uint</span> <span class="token comment">//buffer 中已放入的元素个数</span></pre></td></tr><tr><td data-num="3"></td><td><pre> dataqsiz <span class="token builtin">uint</span> <span class="token comment">// 用户构造 channel 时指定的 buf 大小</span></pre></td></tr><tr><td data-num="4"></td><td><pre> buf unsafe<span class="token punctuation">.</span>Pointer <span class="token comment">// buffer</span></pre></td></tr><tr><td data-num="5"></td><td><pre> elemsize <span class="token builtin">uint16</span> <span class="token comment">//buffer 中每个元素的大小</span></pre></td></tr><tr><td data-num="6"></td><td><pre> closed <span class="token builtin">uint32</span> <span class="token comment">//channel 是否关闭,== 0 代表未 closed</span></pre></td></tr><tr><td data-num="7"></td><td><pre> elemtype <span class="token operator">*</span>_type <span class="token comment">//channel 元素的类型信息</span></pre></td></tr><tr><td data-num="8"></td><td><pre> sendx <span class="token builtin">uint</span> <span class="token comment">//buffer 中已发送的索引位置 send index</span></pre></td></tr><tr><td data-num="9"></td><td><pre> recvx <span class="token builtin">uint</span> <span class="token comment">//buffer 中已接收的索引位置 receive index</span></pre></td></tr><tr><td data-num="10"></td><td><pre> recvq waitq <span class="token comment">// 等待接收的 goroutine list of recv waiters</span></pre></td></tr><tr><td data-num="11"></td><td><pre> sendq waitq <span class="token comment">// 等待发送的 goroutine list of send waiters</span></pre></td></tr><tr><td data-num="12"></td><td><pre></pre></td></tr><tr><td data-num="13"></td><td><pre> lock mutex</pre></td></tr><tr><td data-num="14"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="15"></td><td><pre></pre></td></tr><tr><td data-num="16"></td><td><pre><span class="token keyword">type</span> waitq <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="17"></td><td><pre> first <span class="token operator">*</span>sudog</pre></td></tr><tr><td data-num="18"></td><td><pre> last <span class="token operator">*</span>sudog</pre></td></tr><tr><td data-num="19"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><h2 id="初始化"><a class="anchor" href="#初始化">#</a> 初始化</h2>
<p>如果当前 channel 中不存在缓冲区,那么就只会为 hchan 分配一段内存空间;</p>
<p>如果当前 channel 中存储的类型不是指针类型,就会直接为当前的 Channel 和底层的数组分配一块连续的内存空间;</p>
<p>在默认情况下会单独为 hchan 和 buff 分配内存;</p>
<figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">func</span> <span class="token function">makechan</span><span class="token punctuation">(</span>t <span class="token operator">*</span>chantype<span class="token punctuation">,</span> size <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token operator">*</span>hchan <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> elem <span class="token operator">:=</span> t<span class="token punctuation">.</span>Elem</pre></td></tr><tr><td data-num="3"></td><td><pre></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token comment">// compiler checks this but be safe.</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">if</span> elem<span class="token punctuation">.</span>Size_ <span class="token operator">>=</span> <span class="token number">1</span><span class="token operator"><<</span><span class="token number">16</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token comment">// 元素大小超过限制,抛出异常</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"makechan: invalid channel element type"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="9"></td><td><pre> <span class="token keyword">if</span> hchanSize<span class="token operator">%</span>maxAlign <span class="token operator">!=</span> <span class="token number">0</span> <span class="token operator">||</span> elem<span class="token punctuation">.</span>Align_ <span class="token operator">></span> maxAlign <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="10"></td><td><pre> <span class="token comment">// 确保合理的对齐</span></pre></td></tr><tr><td data-num="11"></td><td><pre> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"makechan: bad alignment"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="13"></td><td><pre></pre></td></tr><tr><td data-num="14"></td><td><pre> <span class="token comment">// 计算内存大小,并检查是否溢出</span></pre></td></tr><tr><td data-num="15"></td><td><pre> mem<span class="token punctuation">,</span> overflow <span class="token operator">:=</span> math<span class="token punctuation">.</span><span class="token function">MulUintptr</span><span class="token punctuation">(</span>elem<span class="token punctuation">.</span>Size_<span class="token punctuation">,</span> <span class="token function">uintptr</span><span class="token punctuation">(</span>size<span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="16"></td><td><pre> <span class="token keyword">if</span> overflow <span class="token operator">||</span> mem <span class="token operator">></span> maxAlloc<span class="token operator">-</span>hchanSize <span class="token operator">||</span> size <span class="token operator"><</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="17"></td><td><pre> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token function">plainError</span><span class="token punctuation">(</span><span class="token string">"makechan: size out of range"</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="18"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="19"></td><td><pre></pre></td></tr><tr><td data-num="20"></td><td><pre> <span class="token comment">// 如果 buff 中的元素不包含指针, Hchan 对 GC 来说并不重要。(如果缓冲区中的元素不包含指针,那么这些元素就不需要被垃圾回收器扫描。)</span></pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token comment">// 缓冲区指向相同的分配区,并且元素类型是持久的。 (大概意思是不会被垃圾回收?)</span></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token comment">// TODO: 重新考虑何时收集器可以移动已分配的对象。</span></pre></td></tr><tr><td data-num="23"></td><td><pre> <span class="token keyword">var</span> c <span class="token operator">*</span>hchan</pre></td></tr><tr><td data-num="24"></td><td><pre> <span class="token keyword">switch</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="25"></td><td><pre> <span class="token keyword">case</span> mem <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">:</span></pre></td></tr><tr><td data-num="26"></td><td><pre> <span class="token comment">// 队列或元素的大小为零。</span></pre></td></tr><tr><td data-num="27"></td><td><pre> c <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">*</span>hchan<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token function">mallocgc</span><span class="token punctuation">(</span>hchanSize<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="28"></td><td><pre> c<span class="token punctuation">.</span>buf <span class="token operator">=</span> c<span class="token punctuation">.</span><span class="token function">raceaddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="29"></td><td><pre> <span class="token keyword">case</span> elem<span class="token punctuation">.</span>PtrBytes <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">:</span></pre></td></tr><tr><td data-num="30"></td><td><pre> <span class="token comment">// 元素不包含指针。</span></pre></td></tr><tr><td data-num="31"></td><td><pre> <span class="token comment">// 一次性分配 hchan 和缓冲区。</span></pre></td></tr><tr><td data-num="32"></td><td><pre> c <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">*</span>hchan<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token function">mallocgc</span><span class="token punctuation">(</span>hchanSize<span class="token operator">+</span>mem<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="33"></td><td><pre> c<span class="token punctuation">.</span>buf <span class="token operator">=</span> <span class="token function">add</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">,</span> hchanSize<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="34"></td><td><pre> <span class="token keyword">default</span><span class="token punctuation">:</span></pre></td></tr><tr><td data-num="35"></td><td><pre> <span class="token comment">// 元素包含指针。</span></pre></td></tr><tr><td data-num="36"></td><td><pre> c <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>hchan<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="37"></td><td><pre> c<span class="token punctuation">.</span>buf <span class="token operator">=</span> <span class="token function">mallocgc</span><span class="token punctuation">(</span>mem<span class="token punctuation">,</span> elem<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="38"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="39"></td><td><pre></pre></td></tr><tr><td data-num="40"></td><td><pre> <span class="token comment">// 初始化其他字段</span></pre></td></tr><tr><td data-num="41"></td><td><pre> c<span class="token punctuation">.</span>elemsize <span class="token operator">=</span> <span class="token function">uint16</span><span class="token punctuation">(</span>elem<span class="token punctuation">.</span>Size_<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="42"></td><td><pre> c<span class="token punctuation">.</span>elemtype <span class="token operator">=</span> elem</pre></td></tr><tr><td data-num="43"></td><td><pre> c<span class="token punctuation">.</span>dataqsiz <span class="token operator">=</span> <span class="token function">uint</span><span class="token punctuation">(</span>size<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="44"></td><td><pre> <span class="token function">lockInit</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">,</span> lockRankHchan<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="45"></td><td><pre></pre></td></tr><tr><td data-num="46"></td><td><pre> <span class="token keyword">if</span> debugChan <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="47"></td><td><pre> <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"makechan: chan="</span><span class="token punctuation">,</span> c<span class="token punctuation">,</span> <span class="token string">"; elemsize="</span><span class="token punctuation">,</span> elem<span class="token punctuation">.</span>Size_<span class="token punctuation">,</span> <span class="token string">"; dataqsiz="</span><span class="token punctuation">,</span> size<span class="token punctuation">,</span> <span class="token string">"\n"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="48"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="49"></td><td><pre> <span class="token keyword">return</span> c</pre></td></tr><tr><td data-num="50"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">func</span> <span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">)</span> <span class="token function">raceaddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span> unsafe<span class="token punctuation">.</span>Pointer <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token comment">// 对通道的类似读写操作视为在这个地址进行操作。避免使用 qcount 或 dataqsiz 的地址,</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token comment">// 因为 len () 和 cap () 内置函数会读取这些地址,而我们不希望它们与像 close () 这样的操作发生 data race。</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token keyword">return</span> unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>buf<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><figure class="highlight go"><figcaption data-lang="go"><span>o</span></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">// empty reports whether a read from c would block (that is, the channel is</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">// empty). It uses a single atomic read of mutable state.</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">func</span> <span class="token function">empty</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">)</span> <span class="token builtin">bool</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token comment">// c.dataqsiz is immutable.</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>dataqsiz <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token keyword">return</span> atomic<span class="token punctuation">.</span><span class="token function">Loadp</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>sendq<span class="token punctuation">.</span>first<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token keyword">return</span> atomic<span class="token punctuation">.</span><span class="token function">Loaduint</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>qcount<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span></pre></td></tr><tr><td data-num="9"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><h2 id="等待队列"><a class="anchor" href="#等待队列">#</a> 等待队列</h2>
<figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">type</span> waitq <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> first <span class="token operator">*</span>sudog</pre></td></tr><tr><td data-num="3"></td><td><pre> last <span class="token operator">*</span>sudog</pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="5"></td><td><pre></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token comment">//sudog 就是 goroutine</span></pre></td></tr></table></figure><figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">//enqueue 方法将一个 sudog 指针加入等待队列中。</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">// 这个函数实现了一个双向链表的尾部插入操作。</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token comment">// 如果队列为空,它会初始化 first 和 last 指针;否则,它会将新元素插入到链表末尾。</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">func</span> <span class="token punctuation">(</span>q <span class="token operator">*</span>waitq<span class="token punctuation">)</span> <span class="token function">enqueue</span><span class="token punctuation">(</span>sgp <span class="token operator">*</span>sudog<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="5"></td><td><pre> sgp<span class="token punctuation">.</span>next <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="6"></td><td><pre> x <span class="token operator">:=</span> q<span class="token punctuation">.</span>last</pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token keyword">if</span> x <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="8"></td><td><pre> sgp<span class="token punctuation">.</span>prev <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="9"></td><td><pre> q<span class="token punctuation">.</span>first <span class="token operator">=</span> sgp</pre></td></tr><tr><td data-num="10"></td><td><pre> q<span class="token punctuation">.</span>last <span class="token operator">=</span> sgp</pre></td></tr><tr><td data-num="11"></td><td><pre> <span class="token keyword">return</span></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="13"></td><td><pre> sgp<span class="token punctuation">.</span>prev <span class="token operator">=</span> x</pre></td></tr><tr><td data-num="14"></td><td><pre> x<span class="token punctuation">.</span>next <span class="token operator">=</span> sgp</pre></td></tr><tr><td data-num="15"></td><td><pre> q<span class="token punctuation">.</span>last <span class="token operator">=</span> sgp</pre></td></tr><tr><td data-num="16"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="17"></td><td><pre></pre></td></tr><tr><td data-num="18"></td><td><pre><span class="token keyword">func</span> <span class="token punctuation">(</span>q <span class="token operator">*</span>waitq<span class="token punctuation">)</span> <span class="token function">dequeue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span>sudog <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="19"></td><td><pre> <span class="token keyword">for</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="20"></td><td><pre> sgp <span class="token operator">:=</span> q<span class="token punctuation">.</span>first</pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token keyword">if</span> sgp <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="23"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="24"></td><td><pre> y <span class="token operator">:=</span> sgp<span class="token punctuation">.</span>next</pre></td></tr><tr><td data-num="25"></td><td><pre> <span class="token keyword">if</span> y <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="26"></td><td><pre> q<span class="token punctuation">.</span>first <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="27"></td><td><pre> q<span class="token punctuation">.</span>last <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="28"></td><td><pre> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="29"></td><td><pre> y<span class="token punctuation">.</span>prev <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="30"></td><td><pre> q<span class="token punctuation">.</span>first <span class="token operator">=</span> y</pre></td></tr><tr><td data-num="31"></td><td><pre> sgp<span class="token punctuation">.</span>next <span class="token operator">=</span> <span class="token boolean">nil</span> <span class="token comment">// 标记为已移除(参见 dequeueSudoG)</span></pre></td></tr><tr><td data-num="32"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="33"></td><td><pre></pre></td></tr><tr><td data-num="34"></td><td><pre> <span class="token comment">// 如果一个 goroutine 由于 select 而被放入此队列,</span></pre></td></tr><tr><td data-num="35"></td><td><pre> <span class="token comment">// 那么在 goroutine 被其他 case 唤醒和获取通道锁之间有一个小的时间窗。</span></pre></td></tr><tr><td data-num="36"></td><td><pre> <span class="token comment">// 一旦它获取了锁,它会将自己从队列中移除,因此我们之后不会再看到它。</span></pre></td></tr><tr><td data-num="37"></td><td><pre> <span class="token comment">// 我们在 G 结构中使用一个标志来告诉我们是否有其他人已经赢得了竞争去唤醒这个 goroutine,</span></pre></td></tr><tr><td data-num="38"></td><td><pre> <span class="token comment">// 但这个 goroutine 还没有从队列中移除自己。</span></pre></td></tr><tr><td data-num="39"></td><td><pre> </pre></td></tr><tr><td data-num="40"></td><td><pre> <span class="token keyword">if</span> sgp<span class="token punctuation">.</span>isSelect <span class="token operator">&&</span> <span class="token operator">!</span>sgp<span class="token punctuation">.</span>g<span class="token punctuation">.</span>selectDone<span class="token punctuation">.</span><span class="token function">CompareAndSwap</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="41"></td><td><pre> <span class="token keyword">continue</span></pre></td></tr><tr><td data-num="42"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="43"></td><td><pre></pre></td></tr><tr><td data-num="44"></td><td><pre> <span class="token keyword">return</span> sgp</pre></td></tr><tr><td data-num="45"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="46"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><h2 id="写入"><a class="anchor" href="#写入">#</a> 写入</h2>
<p>当 channel 的 sendq 队列中包含处于等待状态的 goroutine 时,取出队列头的 goroutine,直接调用 send。</p>
<p>当有缓冲区时,并且缓冲区未满时,写入缓冲区。</p>
<p>当没有缓冲区时或者缓冲区已满时,阻塞接收。</p>
<p>向 已经 close 的 channel 写入数据会导致 panic</p>
<figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">// entry point for c <- x from compiled code.</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">//</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token comment">//go:nosplit</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token keyword">func</span> <span class="token function">chansend1</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">,</span> elem unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token comment">//getcallerpc 获取程序计数器</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token function">chansend</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> elem<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token function">getcallerpc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="8"></td><td><pre></pre></td></tr><tr><td data-num="9"></td><td><pre><span class="token comment">//chansend 是向通道中发送数据的函数 如果 block 参数不为 nil,则不会 sleep,如果无法成功发送则直接 return。</span></pre></td></tr><tr><td data-num="10"></td><td><pre><span class="token comment">//chansend 会向 buff 写数据 or 向等待接收队列写数据</span></pre></td></tr><tr><td data-num="11"></td><td><pre><span class="token keyword">func</span> <span class="token function">chansend</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">,</span> ep unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">,</span> block <span class="token builtin">bool</span><span class="token punctuation">,</span> callerpc <span class="token builtin">uintptr</span><span class="token punctuation">)</span> <span class="token builtin">bool</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token comment">// 如果 channel 是 nil</span></pre></td></tr><tr><td data-num="13"></td><td><pre> <span class="token keyword">if</span> c <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="14"></td><td><pre> <span class="token keyword">if</span> <span class="token operator">!</span>block <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="15"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="16"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="17"></td><td><pre> <span class="token function">gopark</span><span class="token punctuation">(</span><span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> waitReasonChanSendNilChan<span class="token punctuation">,</span> traceBlockForever<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="18"></td><td><pre> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"unreachable"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="19"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="20"></td><td><pre></pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token keyword">if</span> debugChan <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"chansend: chan="</span><span class="token punctuation">,</span> c<span class="token punctuation">,</span> <span class="token string">"\n"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="23"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="24"></td><td><pre></pre></td></tr><tr><td data-num="25"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="26"></td><td><pre> <span class="token function">racereadpc</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">raceaddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> callerpc<span class="token punctuation">,</span> abi<span class="token punctuation">.</span><span class="token function">FuncPCABIInternal</span><span class="token punctuation">(</span>chansend<span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="27"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="28"></td><td><pre> </pre></td></tr><tr><td data-num="29"></td><td><pre> <span class="token comment">// Fast path: 在未获取锁的情况下检查非阻塞操作是否失败。</span></pre></td></tr><tr><td data-num="30"></td><td><pre> <span class="token comment">//</span></pre></td></tr><tr><td data-num="31"></td><td><pre> <span class="token comment">// 在观察到通道未关闭之后,观察通道是否准备好发送。这两个观察都是 a single word-sized 读取(首先是 c.closed 然后是 full ())。</span></pre></td></tr><tr><td data-num="32"></td><td><pre> <span class="token comment">// 即使通道在两次观察之间关闭,关闭的通道也不能从「准备发送」状态变为「未准备好发送」,</span></pre></td></tr><tr><td data-num="33"></td><td><pre> <span class="token comment">// 在这个时刻通道既未关闭也未准备发送。我们的行为就像是在那个时刻观察到了通道,并报告发送无法进行。</span></pre></td></tr><tr><td data-num="34"></td><td><pre> <span class="token comment">//</span></pre></td></tr><tr><td data-num="35"></td><td><pre> <span class="token comment">// 这里的读操作顺序可以重排序:如果我们观察到通道未准备好发送然后观察到它未关闭,这意味着在第一次观察期间通道未关闭。然而,这里没有任何东西保证向前推进。</span></pre></td></tr><tr><td data-num="36"></td><td><pre> <span class="token comment">// 我们依赖于 chanrecv () 和 closechan () 中锁释放的副作用来更新此线程对 c.closed 和 full () 的观察。</span></pre></td></tr><tr><td data-num="37"></td><td><pre> <span class="token keyword">if</span> <span class="token operator">!</span>block <span class="token operator">&&</span> c<span class="token punctuation">.</span>closed <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&&</span> <span class="token function">full</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="38"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="39"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="40"></td><td><pre></pre></td></tr><tr><td data-num="41"></td><td><pre> <span class="token keyword">var</span> t0 <span class="token builtin">int64</span></pre></td></tr><tr><td data-num="42"></td><td><pre> <span class="token keyword">if</span> blockprofilerate <span class="token operator">></span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="43"></td><td><pre> t0 <span class="token operator">=</span> <span class="token function">cputicks</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="44"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="45"></td><td><pre></pre></td></tr><tr><td data-num="46"></td><td><pre> <span class="token comment">// 加锁</span></pre></td></tr><tr><td data-num="47"></td><td><pre> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="48"></td><td><pre></pre></td></tr><tr><td data-num="49"></td><td><pre> <span class="token comment">// 已被关闭</span></pre></td></tr><tr><td data-num="50"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>closed <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="51"></td><td><pre> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="52"></td><td><pre> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token function">plainError</span><span class="token punctuation">(</span><span class="token string">"send on closed channel"</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="53"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="54"></td><td><pre></pre></td></tr><tr><td data-num="55"></td><td><pre> <span class="token keyword">if</span> sg <span class="token operator">:=</span> c<span class="token punctuation">.</span>recvq<span class="token punctuation">.</span><span class="token function">dequeue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sg <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="56"></td><td><pre> <span class="token comment">// 找到了一个等待接收的 goroutine。直接将要发送的值直接传递给 goroutine,绕过通道缓冲区)。</span></pre></td></tr><tr><td data-num="57"></td><td><pre> <span class="token function">send</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> sg<span class="token punctuation">,</span> ep<span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="58"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">true</span></pre></td></tr><tr><td data-num="59"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="60"></td><td><pre></pre></td></tr><tr><td data-num="61"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>qcount <span class="token operator"><</span> c<span class="token punctuation">.</span>dataqsiz <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="62"></td><td><pre> <span class="token comment">//buff 中有可用空间。将待发送的元素入队。</span></pre></td></tr><tr><td data-num="63"></td><td><pre> <span class="token comment">//qp 指向 buf 的 sendx 位置</span></pre></td></tr><tr><td data-num="64"></td><td><pre> qp <span class="token operator">:=</span> <span class="token function">chanbuf</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c<span class="token punctuation">.</span>sendx<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="65"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="66"></td><td><pre> <span class="token function">racenotify</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c<span class="token punctuation">.</span>sendx<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="67"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="68"></td><td><pre> <span class="token comment">// 将数据从 ep 处拷贝到 qp</span></pre></td></tr><tr><td data-num="69"></td><td><pre> <span class="token function">typedmemmove</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> qp<span class="token punctuation">,</span> ep<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="70"></td><td><pre> <span class="token comment">// 发送的游标加 1,如果发送的游标值等于容量值,游标值归 0</span></pre></td></tr><tr><td data-num="71"></td><td><pre> c<span class="token punctuation">.</span>sendx<span class="token operator">++</span></pre></td></tr><tr><td data-num="72"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>sendx <span class="token operator">==</span> c<span class="token punctuation">.</span>dataqsiz <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="73"></td><td><pre> c<span class="token punctuation">.</span>sendx <span class="token operator">=</span> <span class="token number">0</span></pre></td></tr><tr><td data-num="74"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="75"></td><td><pre> <span class="token comment">// 缓冲区的数量加 1</span></pre></td></tr><tr><td data-num="76"></td><td><pre> c<span class="token punctuation">.</span>qcount<span class="token operator">++</span></pre></td></tr><tr><td data-num="77"></td><td><pre> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="78"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">true</span></pre></td></tr><tr><td data-num="79"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="80"></td><td><pre></pre></td></tr><tr><td data-num="81"></td><td><pre> <span class="token comment">//buff 空间已经满了</span></pre></td></tr><tr><td data-num="82"></td><td><pre> <span class="token comment">// 如果不需要阻塞,则直接返回错误</span></pre></td></tr><tr><td data-num="83"></td><td><pre> <span class="token keyword">if</span> <span class="token operator">!</span>block <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="84"></td><td><pre> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="85"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="86"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="87"></td><td><pre></pre></td></tr><tr><td data-num="88"></td><td><pre> <span class="token comment">// 通道阻塞。某个 goroutine 会为我们完成操作。</span></pre></td></tr><tr><td data-num="89"></td><td><pre> gp <span class="token operator">:=</span> <span class="token function">getg</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="90"></td><td><pre> mysg <span class="token operator">:=</span> <span class="token function">acquireSudog</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="91"></td><td><pre> mysg<span class="token punctuation">.</span>releasetime <span class="token operator">=</span> <span class="token number">0</span></pre></td></tr><tr><td data-num="92"></td><td><pre> <span class="token keyword">if</span> t0 <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="93"></td><td><pre> mysg<span class="token punctuation">.</span>releasetime <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span></pre></td></tr><tr><td data-num="94"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="95"></td><td><pre> <span class="token comment">// 阻塞操作</span></pre></td></tr><tr><td data-num="96"></td><td><pre> <span class="token comment">// No stack splits between assigning elem and enqueuing mysg</span></pre></td></tr><tr><td data-num="97"></td><td><pre> <span class="token comment">// on gp.waiting where copystack can find it.</span></pre></td></tr><tr><td data-num="98"></td><td><pre> mysg<span class="token punctuation">.</span>elem <span class="token operator">=</span> ep</pre></td></tr><tr><td data-num="99"></td><td><pre> mysg<span class="token punctuation">.</span>waitlink <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="100"></td><td><pre> mysg<span class="token punctuation">.</span>g <span class="token operator">=</span> gp</pre></td></tr><tr><td data-num="101"></td><td><pre> mysg<span class="token punctuation">.</span>isSelect <span class="token operator">=</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="102"></td><td><pre> mysg<span class="token punctuation">.</span>c <span class="token operator">=</span> c</pre></td></tr><tr><td data-num="103"></td><td><pre> gp<span class="token punctuation">.</span>waiting <span class="token operator">=</span> mysg</pre></td></tr><tr><td data-num="104"></td><td><pre> gp<span class="token punctuation">.</span>param <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="105"></td><td><pre> c<span class="token punctuation">.</span>sendq<span class="token punctuation">.</span><span class="token function">enqueue</span><span class="token punctuation">(</span>mysg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="106"></td><td><pre></pre></td></tr><tr><td data-num="107"></td><td><pre> <span class="token comment">// 向任何试图缩小堆栈的人发出信号,表明我们即将在一个 channel 上放数据。</span></pre></td></tr><tr><td data-num="108"></td><td><pre> <span class="token comment">// 从 G 的状态改变到我们设置 gp.activeStackChans 的这段时间内,堆栈缩小是不安全的。</span></pre></td></tr><tr><td data-num="109"></td><td><pre> gp<span class="token punctuation">.</span>parkingOnChan<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="110"></td><td><pre> <span class="token function">gopark</span><span class="token punctuation">(</span>chanparkcommit<span class="token punctuation">,</span> unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span><span class="token punctuation">,</span> waitReasonChanSend<span class="token punctuation">,</span> traceBlockChanSend<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="111"></td><td><pre> </pre></td></tr><tr><td data-num="112"></td><td><pre> <span class="token comment">// 确保发送的值在接收者将其复制出来之前一直保持有效。</span></pre></td></tr><tr><td data-num="113"></td><td><pre> <span class="token comment">// 虽然 sudog 拥有指向堆栈对象的指针,但 sudog 并不被视为堆栈跟踪的根。</span></pre></td></tr><tr><td data-num="114"></td><td><pre> </pre></td></tr><tr><td data-num="115"></td><td><pre> <span class="token function">KeepAlive</span><span class="token punctuation">(</span>ep<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="116"></td><td><pre></pre></td></tr><tr><td data-num="117"></td><td><pre> <span class="token comment">// 被唤醒</span></pre></td></tr><tr><td data-num="118"></td><td><pre> <span class="token keyword">if</span> mysg <span class="token operator">!=</span> gp<span class="token punctuation">.</span>waiting <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="119"></td><td><pre> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"G waiting list is corrupted"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="120"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="121"></td><td><pre> gp<span class="token punctuation">.</span>waiting <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="122"></td><td><pre> gp<span class="token punctuation">.</span>activeStackChans <span class="token operator">=</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="123"></td><td><pre> closed <span class="token operator">:=</span> <span class="token operator">!</span>mysg<span class="token punctuation">.</span>success</pre></td></tr><tr><td data-num="124"></td><td><pre> gp<span class="token punctuation">.</span>param <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="125"></td><td><pre> <span class="token keyword">if</span> mysg<span class="token punctuation">.</span>releasetime <span class="token operator">></span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="126"></td><td><pre> <span class="token function">blockevent</span><span class="token punctuation">(</span>mysg<span class="token punctuation">.</span>releasetime<span class="token operator">-</span>t0<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="127"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="128"></td><td><pre> mysg<span class="token punctuation">.</span>c <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="129"></td><td><pre> <span class="token function">releaseSudog</span><span class="token punctuation">(</span>mysg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="130"></td><td><pre> <span class="token keyword">if</span> closed <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="131"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>closed <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="132"></td><td><pre> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"chansend: spurious wakeup"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="133"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="134"></td><td><pre> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token function">plainError</span><span class="token punctuation">(</span><span class="token string">"send on closed channel"</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="135"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="136"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">true</span></pre></td></tr><tr><td data-num="137"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">//send 在空通道 c 上执行发送操作。</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">// 发送者发送的值 ep 被复制到接收者 sg。 然后,接收者被唤醒并继续其正常操作。</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token comment">// 通道 c 必须为空且已锁定。send 使用 unlockf 函数解锁 c。</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token comment">//sg 必须已从 c 中出列。ep 必须非空且指向堆或调用者的堆栈。</span></pre></td></tr><tr><td data-num="5"></td><td><pre></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token keyword">func</span> <span class="token function">send</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">,</span> sg <span class="token operator">*</span>sudog<span class="token punctuation">,</span> ep unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">,</span> unlockf <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> skip <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>dataqsiz <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="9"></td><td><pre> <span class="token function">racesync</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> sg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="10"></td><td><pre> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="11"></td><td><pre> <span class="token comment">// 假装经过缓冲区,实际上是直接复制</span></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token comment">// 注意只有在启用竞争条件检测时,才需要增加头 / 尾位置。</span></pre></td></tr><tr><td data-num="13"></td><td><pre> <span class="token function">racenotify</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c<span class="token punctuation">.</span>recvx<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="14"></td><td><pre> <span class="token function">racenotify</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c<span class="token punctuation">.</span>recvx<span class="token punctuation">,</span> sg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="15"></td><td><pre> c<span class="token punctuation">.</span>recvx<span class="token operator">++</span></pre></td></tr><tr><td data-num="16"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>recvx <span class="token operator">==</span> c<span class="token punctuation">.</span>dataqsiz <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="17"></td><td><pre> c<span class="token punctuation">.</span>recvx <span class="token operator">=</span> <span class="token number">0</span></pre></td></tr><tr><td data-num="18"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="19"></td><td><pre> c<span class="token punctuation">.</span>sendx <span class="token operator">=</span> c<span class="token punctuation">.</span>recvx <span class="token comment">// c.sendx = (c.sendx+1) % c.dataqsiz</span></pre></td></tr><tr><td data-num="20"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token keyword">if</span> sg<span class="token punctuation">.</span>elem <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="23"></td><td><pre> <span class="token comment">// 直接拷贝数据</span></pre></td></tr><tr><td data-num="24"></td><td><pre> <span class="token function">sendDirect</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> sg<span class="token punctuation">,</span> ep<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="25"></td><td><pre> sg<span class="token punctuation">.</span>elem <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="26"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="27"></td><td><pre> gp <span class="token operator">:=</span> sg<span class="token punctuation">.</span>g</pre></td></tr><tr><td data-num="28"></td><td><pre> <span class="token function">unlockf</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="29"></td><td><pre> gp<span class="token punctuation">.</span>param <span class="token operator">=</span> unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>sg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="30"></td><td><pre> sg<span class="token punctuation">.</span>success <span class="token operator">=</span> <span class="token boolean">true</span></pre></td></tr><tr><td data-num="31"></td><td><pre> <span class="token keyword">if</span> sg<span class="token punctuation">.</span>releasetime <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="32"></td><td><pre> sg<span class="token punctuation">.</span>releasetime <span class="token operator">=</span> <span class="token function">cputicks</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="33"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="34"></td><td><pre> <span class="token function">goready</span><span class="token punctuation">(</span>gp<span class="token punctuation">,</span> skip<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="35"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">// 在无缓冲或空缓冲通道上发送和接收是唯一一个运行的 goroutine 写入另一个运行 goroutine 的堆栈的操作。</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">// GC 假设堆栈写入仅在 goroutine 运行时发生,并且仅由该 goroutine 完成。</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token comment">// 使用写屏障足以弥补违反这一假设,但写屏障必须起作用。typedmemmove 将调用 bulkBarrierPreWrite,但目标字节不在堆中,因此这无济于事。我们安排调用 memmove 并键入 BitsBulkBarrier。</span></pre></td></tr><tr><td data-num="4"></td><td><pre></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token keyword">func</span> <span class="token function">sendDirect</span><span class="token punctuation">(</span>t <span class="token operator">*</span>_type<span class="token punctuation">,</span> sg <span class="token operator">*</span>sudog<span class="token punctuation">,</span> src unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token comment">// src is on our stack, dst is a slot on another stack.</span></pre></td></tr><tr><td data-num="7"></td><td><pre></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token comment">// Once we read sg.elem out of sg, it will no longer</span></pre></td></tr><tr><td data-num="9"></td><td><pre> <span class="token comment">// be updated if the destination's stack gets copied (shrunk).</span></pre></td></tr><tr><td data-num="10"></td><td><pre> <span class="token comment">// So make sure that no preemption points can happen between read & use.</span></pre></td></tr><tr><td data-num="11"></td><td><pre> dst <span class="token operator">:=</span> sg<span class="token punctuation">.</span>elem</pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token function">typeBitsBulkBarrier</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> <span class="token function">uintptr</span><span class="token punctuation">(</span>dst<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">uintptr</span><span class="token punctuation">(</span>src<span class="token punctuation">)</span><span class="token punctuation">,</span> t<span class="token punctuation">.</span>Size_<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="13"></td><td><pre> <span class="token comment">// No need for cgo write barrier checks because dst is always</span></pre></td></tr><tr><td data-num="14"></td><td><pre> <span class="token comment">// Go memory.</span></pre></td></tr><tr><td data-num="15"></td><td><pre> <span class="token function">memmove</span><span class="token punctuation">(</span>dst<span class="token punctuation">,</span> src<span class="token punctuation">,</span> t<span class="token punctuation">.</span>Size_<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="16"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><h2 id="接收"><a class="anchor" href="#接收">#</a> 接收</h2>
<p>当 channel 被关闭时,缓冲区中没数据,返回</p>
<p>当 channel 被关闭时,sendq 队列中有处于等待状态的 goroutine 取出队列头的 goroutine,直接调用 recv</p>
<p>当 channel 的缓冲区中有数据时,读取缓冲区数据</p>
<p>当 channel 没有被关闭,缓冲区没有数据,阻塞接收。</p>
<figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">// entry points for <- c from compiled code.</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">//go:nosplit</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token keyword">func</span> <span class="token function">chanrecv1</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">,</span> elem unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token function">chanrecv</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> elem<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="6"></td><td><pre> </pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token comment">//go:nosplit</span></pre></td></tr><tr><td data-num="8"></td><td><pre><span class="token keyword">func</span> <span class="token function">chanrecv2</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">,</span> elem unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">)</span> <span class="token punctuation">(</span>received <span class="token builtin">bool</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="9"></td><td><pre> <span class="token boolean">_</span><span class="token punctuation">,</span> received <span class="token operator">=</span> <span class="token function">chanrecv</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> elem<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="10"></td><td><pre> <span class="token keyword">return</span></pre></td></tr><tr><td data-num="11"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="12"></td><td><pre></pre></td></tr><tr><td data-num="13"></td><td><pre><span class="token comment">//chanrecv 在通道 c 上接收数据,并将接收到的数据写入 ep。</span></pre></td></tr><tr><td data-num="14"></td><td><pre><span class="token comment">//ep 可能为 nil,此时接收到的数据将被忽略。</span></pre></td></tr><tr><td data-num="15"></td><td><pre><span class="token comment">// 如果 block == false 且没有可用的元素,则返回 (false, false)。</span></pre></td></tr><tr><td data-num="16"></td><td><pre><span class="token comment">// 否则,如果 c 被关闭,则将 *ep 清零并返回 (true, false)。</span></pre></td></tr><tr><td data-num="17"></td><td><pre><span class="token comment">// 否则,将一个元素填入 *ep 并返回 (true, true)。</span></pre></td></tr><tr><td data-num="18"></td><td><pre><span class="token comment">// 非 nil 的 ep 必须指向堆或调用者的栈。</span></pre></td></tr><tr><td data-num="19"></td><td><pre><span class="token keyword">func</span> <span class="token function">chanrecv</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">,</span> ep unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">,</span> block <span class="token builtin">bool</span><span class="token punctuation">)</span> <span class="token punctuation">(</span>selected<span class="token punctuation">,</span> received <span class="token builtin">bool</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="20"></td><td><pre> <span class="token comment">//raceenabled: 不需要检查 ep,因为它总是在栈上或者由反射新分配的内存中。</span></pre></td></tr><tr><td data-num="21"></td><td><pre></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token keyword">if</span> debugChan <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="23"></td><td><pre> <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"chanrecv: chan="</span><span class="token punctuation">,</span> c<span class="token punctuation">,</span> <span class="token string">"\n"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="24"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="25"></td><td><pre></pre></td></tr><tr><td data-num="26"></td><td><pre> <span class="token keyword">if</span> c <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="27"></td><td><pre> <span class="token keyword">if</span> <span class="token operator">!</span>block <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="28"></td><td><pre> <span class="token keyword">return</span></pre></td></tr><tr><td data-num="29"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="30"></td><td><pre> <span class="token function">gopark</span><span class="token punctuation">(</span><span class="token boolean">nil</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">,</span> waitReasonChanReceiveNilChan<span class="token punctuation">,</span> traceBlockForever<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="31"></td><td><pre> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"unreachable"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="32"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="33"></td><td><pre></pre></td></tr><tr><td data-num="34"></td><td><pre> <span class="token comment">// Fast path: 检查非阻塞操作的失败,而不获取锁。</span></pre></td></tr><tr><td data-num="35"></td><td><pre> <span class="token keyword">if</span> <span class="token operator">!</span>block <span class="token operator">&&</span> <span class="token function">empty</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="36"></td><td><pre> <span class="token comment">// 在观察到 channel !block 后,我们观察通道是否关闭。</span></pre></td></tr><tr><td data-num="37"></td><td><pre> <span class="token comment">// 重新排序这些检查可能会导致在 data race close 时出现不正确的行为。</span></pre></td></tr><tr><td data-num="38"></td><td><pre> </pre></td></tr><tr><td data-num="39"></td><td><pre> <span class="token comment">// 例如,如果 channel 是打开的且不为空,然后被关闭,然后被耗尽,重新排序可能会错误地指示 “打开且为空””</span></pre></td></tr><tr><td data-num="40"></td><td><pre> <span class="token comment">// 为了防止重新排序,我们对这两种检查都使用 atomic loads,并依赖于在同一锁的分离临界区内完成清空和关闭。。</span></pre></td></tr><tr><td data-num="41"></td><td><pre> <span class="token comment">// 当关闭一个有阻塞发送的无缓冲通道时,这一假设会失效,但那是一个错误状态。</span></pre></td></tr><tr><td data-num="42"></td><td><pre> <span class="token keyword">if</span> atomic<span class="token punctuation">.</span><span class="token function">Load</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>closed<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="43"></td><td><pre> <span class="token comment">// 因为通道不能被重新打开,通道稍后的未关闭状态观察意味着它在第一次观察时也未关闭。</span></pre></td></tr><tr><td data-num="44"></td><td><pre> <span class="token comment">// 我们相当于观察到了那一刻的通道并报告接收不能继续。</span></pre></td></tr><tr><td data-num="45"></td><td><pre> <span class="token keyword">return</span></pre></td></tr><tr><td data-num="46"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="47"></td><td><pre></pre></td></tr><tr><td data-num="48"></td><td><pre> <span class="token comment">// 通道不可逆转地关闭了。重新检查通道是否有待接收的数据,</span></pre></td></tr><tr><td data-num="49"></td><td><pre> <span class="token comment">// 这些数据可能在上面的空和关闭检查之间到达。与这种发生竞争时也需要顺序一致性。</span></pre></td></tr><tr><td data-num="50"></td><td><pre> <span class="token keyword">if</span> <span class="token function">empty</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="51"></td><td><pre> <span class="token comment">// 通道不可逆关闭且为空。</span></pre></td></tr><tr><td data-num="52"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="53"></td><td><pre> <span class="token function">raceacquire</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">raceaddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="54"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="55"></td><td><pre> <span class="token keyword">if</span> ep <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="56"></td><td><pre> <span class="token function">typedmemclr</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> ep<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="57"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="58"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="59"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="60"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="61"></td><td><pre></pre></td></tr><tr><td data-num="62"></td><td><pre> <span class="token keyword">var</span> t0 <span class="token builtin">int64</span></pre></td></tr><tr><td data-num="63"></td><td><pre> <span class="token keyword">if</span> blockprofilerate <span class="token operator">></span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="64"></td><td><pre> t0 <span class="token operator">=</span> <span class="token function">cputicks</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="65"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="66"></td><td><pre></pre></td></tr><tr><td data-num="67"></td><td><pre> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="68"></td><td><pre></pre></td></tr><tr><td data-num="69"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>closed <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="70"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>qcount <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="71"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="72"></td><td><pre> <span class="token function">raceacquire</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">raceaddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="73"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="74"></td><td><pre> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="75"></td><td><pre> <span class="token keyword">if</span> ep <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="76"></td><td><pre> <span class="token function">typedmemclr</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> ep<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="77"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="78"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="79"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="80"></td><td><pre> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment">// 通道已关闭,但通道的缓冲区中有数据。</span></pre></td></tr><tr><td data-num="81"></td><td><pre> <span class="token keyword">if</span> sg <span class="token operator">:=</span> c<span class="token punctuation">.</span>sendq<span class="token punctuation">.</span><span class="token function">dequeue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> sg <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token comment">// 刚找到了等待的发送者且仍未关闭。</span></pre></td></tr><tr><td data-num="82"></td><td><pre> <span class="token comment">// 找到等待的发送者。如果缓冲区大小为 0,则从发送者直接接收值。</span></pre></td></tr><tr><td data-num="83"></td><td><pre> <span class="token comment">// 否则,从队列头接收数据,并将发送者的值添加到队列尾(因为队列已满)。</span></pre></td></tr><tr><td data-num="84"></td><td><pre> <span class="token function">recv</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> sg<span class="token punctuation">,</span> ep<span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="85"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token boolean">true</span></pre></td></tr><tr><td data-num="86"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="87"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="88"></td><td><pre></pre></td></tr><tr><td data-num="89"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>qcount <span class="token operator">></span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="90"></td><td><pre> <span class="token comment">// 直接从队列接收</span></pre></td></tr><tr><td data-num="91"></td><td><pre> qp <span class="token operator">:=</span> <span class="token function">chanbuf</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c<span class="token punctuation">.</span>recvx<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="92"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="93"></td><td><pre> <span class="token function">racenotify</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c<span class="token punctuation">.</span>recvx<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="94"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="95"></td><td><pre> <span class="token keyword">if</span> ep <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="96"></td><td><pre> <span class="token function">typedmemmove</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> ep<span class="token punctuation">,</span> qp<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="97"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="98"></td><td><pre> <span class="token function">typedmemclr</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> qp<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="99"></td><td><pre> c<span class="token punctuation">.</span>recvx<span class="token operator">++</span></pre></td></tr><tr><td data-num="100"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>recvx <span class="token operator">==</span> c<span class="token punctuation">.</span>dataqsiz <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="101"></td><td><pre> c<span class="token punctuation">.</span>recvx <span class="token operator">=</span> <span class="token number">0</span></pre></td></tr><tr><td data-num="102"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="103"></td><td><pre> c<span class="token punctuation">.</span>qcount<span class="token operator">--</span></pre></td></tr><tr><td data-num="104"></td><td><pre> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="105"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token boolean">true</span></pre></td></tr><tr><td data-num="106"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="107"></td><td><pre></pre></td></tr><tr><td data-num="108"></td><td><pre> <span class="token keyword">if</span> <span class="token operator">!</span>block <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="109"></td><td><pre> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="110"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="111"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="112"></td><td><pre></pre></td></tr><tr><td data-num="113"></td><td><pre> <span class="token comment">// 没有可用的发送者:在该通道上阻塞。</span></pre></td></tr><tr><td data-num="114"></td><td><pre> gp <span class="token operator">:=</span> <span class="token function">getg</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="115"></td><td><pre> mysg <span class="token operator">:=</span> <span class="token function">acquireSudog</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="116"></td><td><pre> mysg<span class="token punctuation">.</span>releasetime <span class="token operator">=</span> <span class="token number">0</span></pre></td></tr><tr><td data-num="117"></td><td><pre> <span class="token keyword">if</span> t0 <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="118"></td><td><pre> mysg<span class="token punctuation">.</span>releasetime <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span></pre></td></tr><tr><td data-num="119"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="120"></td><td><pre> <span class="token comment">// 在分配 elem 和将 mysg 入队到 gp.waiting 之间不进行堆栈拆分</span></pre></td></tr><tr><td data-num="121"></td><td><pre> <span class="token comment">//copystack 可以找到它。</span></pre></td></tr><tr><td data-num="122"></td><td><pre> mysg<span class="token punctuation">.</span>elem <span class="token operator">=</span> ep</pre></td></tr><tr><td data-num="123"></td><td><pre> mysg<span class="token punctuation">.</span>waitlink <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="124"></td><td><pre> gp<span class="token punctuation">.</span>waiting <span class="token operator">=</span> mysg</pre></td></tr><tr><td data-num="125"></td><td><pre> mysg<span class="token punctuation">.</span>g <span class="token operator">=</span> gp</pre></td></tr><tr><td data-num="126"></td><td><pre> mysg<span class="token punctuation">.</span>isSelect <span class="token operator">=</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="127"></td><td><pre> mysg<span class="token punctuation">.</span>c <span class="token operator">=</span> c</pre></td></tr><tr><td data-num="128"></td><td><pre> gp<span class="token punctuation">.</span>param <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="129"></td><td><pre> c<span class="token punctuation">.</span>recvq<span class="token punctuation">.</span><span class="token function">enqueue</span><span class="token punctuation">(</span>mysg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="130"></td><td><pre> <span class="token comment">// 向任何试图缩小堆栈的人发出信号,表明我们即将在通道上阻塞。</span></pre></td></tr><tr><td data-num="131"></td><td><pre> <span class="token comment">// 此 G 的状态改变与我们设置 gp.activeStackChans 之间的时间窗口对栈缩小不安全</span></pre></td></tr><tr><td data-num="132"></td><td><pre> gp<span class="token punctuation">.</span>parkingOnChan<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="133"></td><td><pre> <span class="token function">gopark</span><span class="token punctuation">(</span>chanparkcommit<span class="token punctuation">,</span> unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span><span class="token punctuation">,</span> waitReasonChanReceive<span class="token punctuation">,</span> traceBlockChanRecv<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="134"></td><td><pre></pre></td></tr><tr><td data-num="135"></td><td><pre> <span class="token comment">// 被唤醒</span></pre></td></tr><tr><td data-num="136"></td><td><pre> <span class="token keyword">if</span> mysg <span class="token operator">!=</span> gp<span class="token punctuation">.</span>waiting <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="137"></td><td><pre> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"G waiting list is corrupted"</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="138"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="139"></td><td><pre> gp<span class="token punctuation">.</span>waiting <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="140"></td><td><pre> gp<span class="token punctuation">.</span>activeStackChans <span class="token operator">=</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="141"></td><td><pre> <span class="token keyword">if</span> mysg<span class="token punctuation">.</span>releasetime <span class="token operator">></span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="142"></td><td><pre> <span class="token function">blockevent</span><span class="token punctuation">(</span>mysg<span class="token punctuation">.</span>releasetime<span class="token operator">-</span>t0<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="143"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="144"></td><td><pre> success <span class="token operator">:=</span> mysg<span class="token punctuation">.</span>success</pre></td></tr><tr><td data-num="145"></td><td><pre> gp<span class="token punctuation">.</span>param <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="146"></td><td><pre> mysg<span class="token punctuation">.</span>c <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="147"></td><td><pre> <span class="token function">releaseSudog</span><span class="token punctuation">(</span>mysg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="148"></td><td><pre> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">,</span> success</pre></td></tr><tr><td data-num="149"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">//recv 处理满通道 c 上的接收操作。</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token comment">// 包含 2 部分:</span></pre></td></tr><tr><td data-num="3"></td><td><pre><span class="token comment">// 1. 由发送者 sg 发送的值被放入通道,并唤醒发送者去执行其他操作。</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token comment">// 2. 接收者(当前 Goroutine)接收的值写入 ep。</span></pre></td></tr><tr><td data-num="5"></td><td><pre><span class="token comment">//</span></pre></td></tr><tr><td data-num="6"></td><td><pre><span class="token comment">// 对于同步通道,两个值相同。</span></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token comment">// 对于异步通道,接收者从通道缓冲区获取其数据,而发送者的数据被放入通道缓冲区。</span></pre></td></tr><tr><td data-num="8"></td><td><pre><span class="token comment">// 通道 c 必须已满且被锁定。recv 用 unlockf 解锁 c。</span></pre></td></tr><tr><td data-num="9"></td><td><pre><span class="token comment">//sg 必须已出列。</span></pre></td></tr><tr><td data-num="10"></td><td><pre><span class="token comment">// 非 nil 的 ep 必须指向堆或调用者的栈。</span></pre></td></tr><tr><td data-num="11"></td><td><pre><span class="token keyword">func</span> <span class="token function">recv</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">,</span> sg <span class="token operator">*</span>sudog<span class="token punctuation">,</span> ep unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">,</span> unlockf <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> skip <span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>dataqsiz <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="13"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="14"></td><td><pre> <span class="token function">racesync</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> sg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="15"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="16"></td><td><pre> <span class="token keyword">if</span> ep <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="17"></td><td><pre> <span class="token comment">// 从发送者拷贝数据</span></pre></td></tr><tr><td data-num="18"></td><td><pre> <span class="token function">recvDirect</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> sg<span class="token punctuation">,</span> ep<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="19"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="20"></td><td><pre> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token comment">// 队列已满。取队列头的项目。使发送者将其项目入队到队列尾。</span></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token comment">// 由于队列已满,这两个位置都是同一个槽。</span></pre></td></tr><tr><td data-num="23"></td><td><pre> qp <span class="token operator">:=</span> <span class="token function">chanbuf</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c<span class="token punctuation">.</span>recvx<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="24"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="25"></td><td><pre> <span class="token function">racenotify</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c<span class="token punctuation">.</span>recvx<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="26"></td><td><pre> <span class="token function">racenotify</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c<span class="token punctuation">.</span>recvx<span class="token punctuation">,</span> sg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="27"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="28"></td><td><pre> <span class="token comment">// 从队列复制数据到接收者</span></pre></td></tr><tr><td data-num="29"></td><td><pre> <span class="token keyword">if</span> ep <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="30"></td><td><pre> <span class="token function">typedmemmove</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> ep<span class="token punctuation">,</span> qp<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="31"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="32"></td><td><pre> <span class="token comment">// 从发送者复制数据到队列</span></pre></td></tr><tr><td data-num="33"></td><td><pre> <span class="token function">typedmemmove</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> qp<span class="token punctuation">,</span> sg<span class="token punctuation">.</span>elem<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="34"></td><td><pre> c<span class="token punctuation">.</span>recvx<span class="token operator">++</span></pre></td></tr><tr><td data-num="35"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>recvx <span class="token operator">==</span> c<span class="token punctuation">.</span>dataqsiz <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="36"></td><td><pre> c<span class="token punctuation">.</span>recvx <span class="token operator">=</span> <span class="token number">0</span></pre></td></tr><tr><td data-num="37"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="38"></td><td><pre> c<span class="token punctuation">.</span>sendx <span class="token operator">=</span> c<span class="token punctuation">.</span>recvx <span class="token comment">// c.sendx = (c.sendx+1) % c.dataqsiz</span></pre></td></tr><tr><td data-num="39"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="40"></td><td><pre> sg<span class="token punctuation">.</span>elem <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="41"></td><td><pre> gp <span class="token operator">:=</span> sg<span class="token punctuation">.</span>g</pre></td></tr><tr><td data-num="42"></td><td><pre> <span class="token function">unlockf</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="43"></td><td><pre> gp<span class="token punctuation">.</span>param <span class="token operator">=</span> unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>sg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="44"></td><td><pre> sg<span class="token punctuation">.</span>success <span class="token operator">=</span> <span class="token boolean">true</span></pre></td></tr><tr><td data-num="45"></td><td><pre> <span class="token keyword">if</span> sg<span class="token punctuation">.</span>releasetime <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="46"></td><td><pre> sg<span class="token punctuation">.</span>releasetime <span class="token operator">=</span> <span class="token function">cputicks</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="47"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="48"></td><td><pre> <span class="token function">goready</span><span class="token punctuation">(</span>gp<span class="token punctuation">,</span> skip<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="49"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><h2 id="关闭"><a class="anchor" href="#关闭">#</a> 关闭</h2>
<p>在函数执行的最后会为所有被阻塞的 Goroutine 调用 goready 函数重新对这些协程进行调度.</p>
<p>close nil channel 和 close 已经关闭的 channel 会导致 panic</p>
<figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">//go:linkname reflect_chanclose reflect.chanclose</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">func</span> <span class="token function">reflect_chanclose</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token function">closechan</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="4"></td><td><pre><span class="token punctuation">}</span></pre></td></tr><tr><td data-num="5"></td><td><pre></pre></td></tr><tr><td data-num="6"></td><td><pre></pre></td></tr><tr><td data-num="7"></td><td><pre><span class="token keyword">func</span> <span class="token function">closechan</span><span class="token punctuation">(</span>c <span class="token operator">*</span>hchan<span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token keyword">if</span> c <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="9"></td><td><pre> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token function">plainError</span><span class="token punctuation">(</span><span class="token string">"close of nil channel"</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="10"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="11"></td><td><pre></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="13"></td><td><pre> <span class="token keyword">if</span> c<span class="token punctuation">.</span>closed <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="14"></td><td><pre> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="15"></td><td><pre> <span class="token function">panic</span><span class="token punctuation">(</span><span class="token function">plainError</span><span class="token punctuation">(</span><span class="token string">"close of closed channel"</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="16"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="17"></td><td><pre></pre></td></tr><tr><td data-num="18"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="19"></td><td><pre> callerpc <span class="token operator">:=</span> <span class="token function">getcallerpc</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="20"></td><td><pre> <span class="token function">racewritepc</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">raceaddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> callerpc<span class="token punctuation">,</span> abi<span class="token punctuation">.</span><span class="token function">FuncPCABIInternal</span><span class="token punctuation">(</span>closechan<span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token function">racerelease</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span><span class="token function">raceaddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="23"></td><td><pre></pre></td></tr><tr><td data-num="24"></td><td><pre> c<span class="token punctuation">.</span>closed <span class="token operator">=</span> <span class="token number">1</span></pre></td></tr><tr><td data-num="25"></td><td><pre></pre></td></tr><tr><td data-num="26"></td><td><pre> <span class="token keyword">var</span> glist gList</pre></td></tr><tr><td data-num="27"></td><td><pre></pre></td></tr><tr><td data-num="28"></td><td><pre> <span class="token comment">// 释放所有在接收队列中的 goroutine</span></pre></td></tr><tr><td data-num="29"></td><td><pre> <span class="token keyword">for</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="30"></td><td><pre> sg <span class="token operator">:=</span> c<span class="token punctuation">.</span>recvq<span class="token punctuation">.</span><span class="token function">dequeue</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="31"></td><td><pre> <span class="token keyword">if</span> sg <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="32"></td><td><pre> <span class="token keyword">break</span></pre></td></tr><tr><td data-num="33"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="34"></td><td><pre> <span class="token keyword">if</span> sg<span class="token punctuation">.</span>elem <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="35"></td><td><pre> <span class="token function">typedmemclr</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>elemtype<span class="token punctuation">,</span> sg<span class="token punctuation">.</span>elem<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="36"></td><td><pre> sg<span class="token punctuation">.</span>elem <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="37"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="38"></td><td><pre> <span class="token keyword">if</span> sg<span class="token punctuation">.</span>releasetime <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="39"></td><td><pre> sg<span class="token punctuation">.</span>releasetime <span class="token operator">=</span> <span class="token function">cputicks</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="40"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="41"></td><td><pre> <span class="token comment">// 将当前接收 goroutine 准备好</span></pre></td></tr><tr><td data-num="42"></td><td><pre> gp <span class="token operator">:=</span> sg<span class="token punctuation">.</span>g</pre></td></tr><tr><td data-num="43"></td><td><pre> gp<span class="token punctuation">.</span>param <span class="token operator">=</span> unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>sg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="44"></td><td><pre> sg<span class="token punctuation">.</span>success <span class="token operator">=</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="45"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="46"></td><td><pre> <span class="token function">raceacquireg</span><span class="token punctuation">(</span>gp<span class="token punctuation">,</span> c<span class="token punctuation">.</span><span class="token function">raceaddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="47"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="48"></td><td><pre> <span class="token comment">// 将准备好的 goroutine 推到列表中</span></pre></td></tr><tr><td data-num="49"></td><td><pre> glist<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>gp<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="50"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="51"></td><td><pre></pre></td></tr><tr><td data-num="52"></td><td><pre> <span class="token comment">// 释放所有在发送队列中的 goroutine (they will panic)</span></pre></td></tr><tr><td data-num="53"></td><td><pre> <span class="token keyword">for</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="54"></td><td><pre> sg <span class="token operator">:=</span> c<span class="token punctuation">.</span>sendq<span class="token punctuation">.</span><span class="token function">dequeue</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="55"></td><td><pre> <span class="token keyword">if</span> sg <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="56"></td><td><pre> <span class="token keyword">break</span></pre></td></tr><tr><td data-num="57"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="58"></td><td><pre> sg<span class="token punctuation">.</span>elem <span class="token operator">=</span> <span class="token boolean">nil</span></pre></td></tr><tr><td data-num="59"></td><td><pre> <span class="token keyword">if</span> sg<span class="token punctuation">.</span>releasetime <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="60"></td><td><pre> sg<span class="token punctuation">.</span>releasetime <span class="token operator">=</span> <span class="token function">cputicks</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="61"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="62"></td><td><pre> gp <span class="token operator">:=</span> sg<span class="token punctuation">.</span>g</pre></td></tr><tr><td data-num="63"></td><td><pre> gp<span class="token punctuation">.</span>param <span class="token operator">=</span> unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>sg<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="64"></td><td><pre> sg<span class="token punctuation">.</span>success <span class="token operator">=</span> <span class="token boolean">false</span></pre></td></tr><tr><td data-num="65"></td><td><pre> <span class="token keyword">if</span> raceenabled <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="66"></td><td><pre> <span class="token function">raceacquireg</span><span class="token punctuation">(</span>gp<span class="token punctuation">,</span> c<span class="token punctuation">.</span><span class="token function">raceaddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="67"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="68"></td><td><pre> glist<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>gp<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="69"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="70"></td><td><pre> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token operator">&</span>c<span class="token punctuation">.</span>lock<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="71"></td><td><pre></pre></td></tr><tr><td data-num="72"></td><td><pre> <span class="token comment">// 现在已经 unlock 准备所有 goroutine。</span></pre></td></tr><tr><td data-num="73"></td><td><pre> <span class="token keyword">for</span> <span class="token operator">!</span>glist<span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="74"></td><td><pre> gp <span class="token operator">:=</span> glist<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="75"></td><td><pre> gp<span class="token punctuation">.</span>schedlink <span class="token operator">=</span> <span class="token number">0</span></pre></td></tr><tr><td data-num="76"></td><td><pre> <span class="token function">goready</span><span class="token punctuation">(</span>gp<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="77"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="78"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><h2 id="参考文章"><a class="anchor" href="#参考文章">#</a> 参考文章</h2>
<p><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL0xlb1lhbmc5MC9Hb2xhbmctSW50ZXJuYWwtTm90ZXMvYmxvYi9tYXN0ZXIvR28lMjBDaGFubmVsLm1k">https://github.com/LeoYang90/Golang-Internal-Notes/blob/master/Go Channel.md</span></p>
<p><span class="exturl" data-url="aHR0cDovL2xlZ2VuZHRrbC5jb20vMjAxNy8wNy8zMC91bmRlcnN0YW5kaW5nLWdvbGFuZy1jaGFubmVsLw==">http://legendtkl.com/2017/07/30/understanding-golang-channel/</span></p>