社会评论家 發表於 2019-11-20 09:31:00

go中通道channel的使用及原理

<h3>1. channel的使用</h3>
<p>  channel,通道。golang中用于数据传递的一种数据结构。是golang中一种传递数据的方式,也可用作事件通知。</p>
<h4>1.1 声明、传值、关闭</h4>
<p>  使用chan关键字声明一个通道,在使用前必须先创建,操作符&nbsp;<code>&lt;-</code>&nbsp;用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">声明和创建</span>
<span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 255, 1)">var</span> ch chan <span style="color: rgba(0, 0, 255, 1)">int</span>      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 声明一个传递int类型的channel</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> ch := make(chan <span style="color: rgba(0, 0, 255, 1)">int</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用内置函数make()定义一个channel</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> ch2 := make(chan <span style="color: rgba(0, 0, 255, 1)">interface</span>{})         <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个空接口类型的通道, 可以存放任意格式</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> type Equip <span style="color: rgba(0, 0, 255, 1)">struct</span>{ <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 一些字段 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> ch2 := make(chan *Equip)             <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建Equip指针类型的通道, 可以存放*Equip
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">传值</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> ch &lt;- value          <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将一个数据value写入至channel,这会导致阻塞,直到有其他goroutine从这个channel中读取数据</span>
<span style="color: rgba(0, 128, 128, 1)">11</span> value := &lt;-ch      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从channel中读取数据,如果channel之前没有写入数据,也会导致阻塞,直到channel中被写入数据为止</span>
<span style="color: rgba(0, 128, 128, 1)">12</span>
<span style="color: rgba(0, 128, 128, 1)">13</span> ch := make(chan <span style="color: rgba(0, 0, 255, 1)">interface</span>{})<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个空接口通道</span>
<span style="color: rgba(0, 128, 128, 1)">14</span> ch &lt;- <span style="color: rgba(128, 0, 128, 1)">0</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将0放入通道中</span>
<span style="color: rgba(0, 128, 128, 1)">15</span> ch &lt;- <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hello</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将hello字符串放入通道中
</span><span style="color: rgba(0, 128, 128, 1)">16</span>
<span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">关闭</span>
<span style="color: rgba(0, 128, 128, 1)">18</span> close(ch)            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关闭channel</span></pre>
</div>
<p>  把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。Go 程序运行时能智能地发现一些永远无法发送成功的语句并报错:</p>
<div class="cnblogs_code">
<pre>fatal error: all goroutines are asleep - deadlock!
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">运行时发现所有的 goroutine(包括main)都处于等待 goroutine。</span></pre>
</div>
<h4>1.2 四种重要的通道使用方式</h4>
<h5>无缓冲通道</h5>
<p>  通道默认是无缓冲的,无缓冲通道上的发送操作将会被阻塞,直到有其他goroutine从对应的通道上执行接收操作,数据传送完成,通道继续工作。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">package main

import (
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fmt</span><span style="color: rgba(128, 0, 0, 1)">"</span>
    <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">time</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
)
</span><span style="color: rgba(0, 0, 255, 1)">var</span> done chan <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)">
func HelloWorld() {
    fmt.Println(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello world goroutine</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    // time.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">1</span>*<span style="color: rgba(0, 0, 0, 1)">time.Second)
    done </span>&lt;- <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
func main() {
    done </span>= make(chan <span style="color: rgba(0, 0, 255, 1)">bool</span>)<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个channel</span>
<span style="color: rgba(0, 0, 0, 1)">    go HelloWorld()
    </span>&lt;-<span style="color: rgba(0, 0, 0, 1)">done
}</span></pre>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Hello world goroutine</span></pre>
</div>
<p>  由于main不会等goroutine执行结束才返回,所以可加sleep输出为了可以看到goroutine的输出内容,那么在这里由于是阻塞的,所以无需sleep。</p>
<p>  将代码中”done &lt;- true”和”&lt;-done”,去掉再执行,没有上面的输出内容。</p>
<h5>管道</h5>
<p>  通道可以用来连接goroutine,一边的输入是另一边输出。这就叫做管道:</p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/1069650/201911/1069650-20191119152736185-950738106.png"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)">import (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fmt</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">time</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">var</span> echo chan <span style="color: rgba(0, 0, 255, 1)">string</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 255, 1)">var</span> receive chan <span style="color: rgba(0, 0, 255, 1)">string</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义goroutine 1 </span>
<span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 0, 1)">func Echo() {
</span><span style="color: rgba(0, 128, 128, 1)">12</span>   time.Sleep(<span style="color: rgba(128, 0, 128, 1)">1</span>*<span style="color: rgba(0, 0, 0, 1)">time.Second)
</span><span style="color: rgba(0, 128, 128, 1)">13</span>   echo &lt;- <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">这是一次测试</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">15</span>
<span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义goroutine 2</span>
<span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)">func Receive() {
</span><span style="color: rgba(0, 128, 128, 1)">18</span>   temp := &lt;- echo <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 阻塞等待echo的通道的返回</span>
<span style="color: rgba(0, 128, 128, 1)">19</span>   receive &lt;-<span style="color: rgba(0, 0, 0, 1)"> temp
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">21</span>
<span style="color: rgba(0, 128, 128, 1)">22</span>
<span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)">24</span>   echo = make(chan <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">25</span>   receive = make(chan <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">26</span>
<span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 0, 1)">    go Echo()
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)">    go Receive()
</span><span style="color: rgba(0, 128, 128, 1)">29</span>
<span style="color: rgba(0, 128, 128, 1)">30</span>   getStr := &lt;-receive   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收goroutine 2的返回</span>
<span style="color: rgba(0, 128, 128, 1)">31</span>
<span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)">    fmt.Println(getStr)
</span><span style="color: rgba(0, 128, 128, 1)">33</span> }</pre>
</div>
<p>  输出字符串:"这是一次测试"。</p>
<p>  在这里不一定要去关闭channel,因为底层的垃圾回收机制会根据它是否可以访问来决定是否自动回收它。(这里不是根据channel是否关闭来决定的)</p>
<h5>单向通道类型</h5>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)">import (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fmt</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">time</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义goroutine 1</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span> func Echo(<span style="color: rgba(0, 0, 255, 1)">out</span> chan&lt;- <span style="color: rgba(0, 0, 255, 1)">string</span>) {   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义输出通道类型</span>
<span style="color: rgba(0, 128, 128, 1)">10</span>   time.Sleep(<span style="color: rgba(128, 0, 128, 1)">1</span>*<span style="color: rgba(0, 0, 0, 1)">time.Second)
</span><span style="color: rgba(0, 128, 128, 1)">11</span>   <span style="color: rgba(0, 0, 255, 1)">out</span> &lt;- <span style="color: rgba(128, 0, 0, 1)">"这又是一次测试</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)">12</span>   close(<span style="color: rgba(0, 0, 255, 1)">out</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">14</span>
<span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义goroutine 2</span>
<span style="color: rgba(0, 128, 128, 1)">16</span> func Receive(<span style="color: rgba(0, 0, 255, 1)">out</span> chan&lt;- <span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 0, 255, 1)">in</span> &lt;-chan <span style="color: rgba(0, 0, 255, 1)">string</span>) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义输出通道类型和输入类型</span>
<span style="color: rgba(0, 128, 128, 1)">17</span>   temp := &lt;-<span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 阻塞等待echo的通道的返回</span>
<span style="color: rgba(0, 128, 128, 1)">18</span>   <span style="color: rgba(0, 0, 255, 1)">out</span> &lt;-<span style="color: rgba(0, 0, 0, 1)"> temp
</span><span style="color: rgba(0, 128, 128, 1)">19</span>   close(<span style="color: rgba(0, 0, 255, 1)">out</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">21</span>
<span style="color: rgba(0, 128, 128, 1)">22</span>
<span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)">24</span>   echo := make(chan <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">25</span>   receive := make(chan <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">26</span>
<span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 0, 1)">    go Echo(echo)
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)">    go Receive(receive, echo)
</span><span style="color: rgba(0, 128, 128, 1)">29</span>
<span style="color: rgba(0, 128, 128, 1)">30</span>   getStr := &lt;-receive   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收goroutine 2的返回</span>
<span style="color: rgba(0, 128, 128, 1)">31</span>
<span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)">    fmt.Println(getStr)
</span><span style="color: rgba(0, 128, 128, 1)">33</span> }</pre>
</div>
<p>  输出:这又是一次测试。</p>
<h5>缓冲管道</h5>
<p>  goroutine的通道默认是是阻塞的,那么有什么办法可以缓解阻塞?&nbsp;答案是:加一个缓冲区。</p>
<p>  创建一个缓冲通道:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> ch := make(chan <span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(128, 0, 128, 1)">3</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建了缓冲区为3的通道
</span><span style="color: rgba(0, 128, 128, 1)">2</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">==</span>
<span style="color: rgba(0, 128, 128, 1)">4</span> len(ch)   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 长度计算</span>
<span style="color: rgba(0, 128, 128, 1)">5</span> cap(ch)   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 容量计算</span></pre>
</div>
<p>  缓冲通道传递数据示意图:</p>
<p><img src="https://img2018.cnblogs.com/blog/1069650/201911/1069650-20191119154715766-2032371384.png">&nbsp;</p>
<h3>2. 内部结构&nbsp;</h3>
<p>  Go语言channel是first-class的,意味着它可以被存储到变量中,可以作为参数传递给函数,也可以作为函数的返回值返回。作为Go语言的核心特征之一,虽然channel看上去很高端,但是其实它仅仅就是一个数据结构而已,具体定义在&nbsp;<code>$GOROOT/src/runtime/chan.go</code>里。如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> type hchan <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   qcount <span style="color: rgba(0, 0, 255, 1)">uint</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 队列中的总数据</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span>   dataqsiz <span style="color: rgba(0, 0, 255, 1)">uint</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 循环队列的大小</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span>   buf <span style="color: rgba(0, 0, 255, 1)">unsafe</span>.Pointer <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 指向dataqsiz元素数组 指向环形队列</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   elemsize uint16<span style="color: rgba(0, 128, 0, 1)">//</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">closed uint32
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>   elemtype *_type <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 元素类型</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span>   sendx <span style="color: rgba(0, 0, 255, 1)">uint</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送索引</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span>   recvx <span style="color: rgba(0, 0, 255, 1)">uint</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收索引</span>
<span style="color: rgba(0, 128, 128, 1)">10</span>   recvq waitq <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接待员名单, 因recv而阻塞的等待队列。</span>
<span style="color: rgba(0, 128, 128, 1)">11</span>   sendq waitq <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送服务员列表, 因send而阻塞的等待队列。
</span><span style="color: rgba(0, 128, 128, 1)">12</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">锁定保护hchan中的所有字段,以及几个在此通道上阻止的sudogs中的字段。
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 128, 0, 1)">&nbsp;&nbsp;</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">按住此锁定时不要更改另一个G的状态(尤其是不要准备G),因为这可能会导致死锁堆栈缩小。</span>
<span style="color: rgba(0, 128, 128, 1)">14</span>   <span style="color: rgba(0, 0, 255, 1)">lock</span><span style="color: rgba(0, 0, 0, 1)"> mutex
</span><span style="color: rgba(0, 128, 128, 1)">15</span> }</pre>
</div>
<p>&nbsp;  其核心是存放channel数据的环形队列,由qcount和elemsize分别指定了队列的容量和当前使用量。dataqsize是队列的大小。elemalg是元素操作的一个Alg结构体,记录下元素的操作,如copy函数,equal函数,hash函数等。</p>
<p>  如果是带缓冲区的chan,则缓冲区数据实际上是紧接着Hchan结构体中分配的。不带缓冲的 channel ,环形队列 size 则为 0。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> c = (Hchan*)runtime.mal(n + hint*elem-&gt;size);</pre>
</div>
<p>  另一重要部分是recvq和sendq两个双向链表,前者是等待读通道(&lt;-channel)的goroutine队列,后者是等待写通道(channel &lt;- xxx)的goroutine队列。若一个goroutine阻塞于channel了,那么它就被挂在recvq或sendq队列中。WaitQ是链表的定义,包含一个头结点和一个尾结点:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">    WaitQ
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)">3</span>   SudoG*<span style="color: rgba(0, 0, 0, 1)">    first;
</span><span style="color: rgba(0, 128, 128, 1)">4</span>   SudoG*<span style="color: rgba(0, 0, 0, 1)">    last;
</span><span style="color: rgba(0, 128, 128, 1)">5</span> };</pre>
</div>
<p>  队列中的每个成员是一个SudoG结构体变量:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">    SudoG
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)">3</span>   G*    g;      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> g和selgen构成</span>
<span style="color: rgba(0, 128, 128, 1)">4</span>   uint32    selgen;      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 指向g的弱指针</span>
<span style="color: rgba(0, 128, 128, 1)">5</span>   SudoG*<span style="color: rgba(0, 0, 0, 1)">    link;
</span><span style="color: rgba(0, 128, 128, 1)">6</span> <span style="color: rgba(0, 0, 0, 1)">    int64    releasetime;
</span><span style="color: rgba(0, 128, 128, 1)">7</span>   <span style="color: rgba(0, 0, 255, 1)">byte</span>*    elem;      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 数据元素</span>
<span style="color: rgba(0, 128, 128, 1)">8</span> };</pre>
</div>
<p>  SudoG里主要结构是一个g和一个elem。elem用于存储goroutine的数据。读通道时,数据会从Hchan的buf队列中拷贝到SudoG的elem域。写通道时,数据则是由SudoG的elem域拷贝到Hchan的队列中。</p>
<p><img src="https://img2018.cnblogs.com/blog/1069650/201911/1069650-20191119161656861-2110164001.png">&nbsp;</p>
<ul>
<li><code>buf</code>是有缓冲的channel所特有的结构,用来存储缓存数据。是个循环链表</li>
<li><code>sendx</code>和<code>recvx</code>用于记录<code>buf</code>这个循环链表中的发送或者接收的index</li>
<li><code>lock</code>是个互斥锁。</li>
</ul>
<p>从最基本的开始-创建channel</p>
<p>创建一个缓冲channel</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> ch := make(chan <span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(128, 0, 128, 1)">3</span>)<span style="color: rgba(0, 128, 0, 1)">//</span></pre>
</div>
<p class="comments-section">底层操作就是从Heap中分配一块内存,在内存中实例化一个<code>hchan</code>的结构体,并返回一个ch指针,使用 channel时,在函数之间的传递就是这个指针,这就是为什么函数传递中无需使用channel的指针,而直接用channel就行了,因为channel本身就是一个指针。  </p>
<p class="comments-section">基本的写channel操作,在底层运行时库中对应的是一个runtime.chansend函数。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> chan &lt;- value</pre>
</div>
<pre>  在运行时库中会执行:</pre>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">void</span> runtime·chansend(ChanType *t, Hchan *c, <span style="color: rgba(0, 0, 255, 1)">byte</span> *ep, <span style="color: rgba(0, 0, 255, 1)">bool</span> *pres, <span style="color: rgba(0, 0, 255, 1)">void</span> *pc)</pre>
</div>
<p class="comments-section">  其中c就是channel,ep是取变量v的地址。这里的传值约定是调用者负责分配好ep的空间,仅需要简单的取变量地址就够了。pres参数是在select中的通道操作使用的。</p>
<p class="comments-section">  这个函数首先判断是同步或异步。同步chan不带缓冲区,可能写阻塞,而异步chan带缓冲区,只有缓冲区满才阻塞。在同步的情况下,首先查看Hchan结构体中的recvq链表时否为空,即是否有因为读该管道而阻塞的goroutine。如果有则可以正常写channel,否则操作会阻塞。</p>
<p class="comments-section">  recvq不为空时,将一个SudoG结构体出队列,将传给通道的数据(函数参数ep)拷贝到SudoG结构体中的elem域,并将SudoG中的g放到就绪队列中,状态置为ready,然后函数返回。如果recvq为空,将当前goroutine阻塞。此时将一个SudoG结构体挂到通道的sendq链表中,这个SudoG中的elem域是参数eq,SudoG中的g是当前的goroutine。当前goroutine会被设置为waiting状态并挂到等待队列中。</p>
<p class="comments-section">  异步时,如果缓冲区满,要将当前goroutine和数据一起作为SudoG结构体挂在sendq队列中。在channel缓冲区不满的情况,直接将数据放到channel的缓冲区中,调用者返回。</p>
<p class="comments-section">实现细节:</p>
<ul>
<li class="comments-section">当使用<code>send (ch &lt;- xx)</code>或者<code>recv ( &lt;-ch)</code>的时候,首先要<strong>锁住</strong><code>hchan</code>这个结构体。(lock字段);</li>
<li class="comments-section">向缓冲区写数据,按链表顺序存放在buf中,直到缓冲区满;</li>
<li class="comments-section">取数据的时候按链表顺序读取,符合FIFO的原则。</li>
</ul>
<div class="cl-preview-section">
<p>读写操作的细节都可以细化为:</p>
</div>
<div class="cl-preview-section">
<ul>
<li>第一,加锁</li>
<li>第二,把数据从goroutine中copy到“队列”中(或者从队列中copy到goroutine中)。</li>
<li>第三,释放锁</li>
</ul>
</div>
<p class="comments-section">  读channel操作也是类似的,对应的函数是runtime.chansend。基本过程类似。</p>
<p>  当协程尝试从未关闭的 channel 中读取数据时,内部的操作如下:</p>
<ul>
<li>当 buf 非空时,此时 recvq 必为空,buf 弹出一个元素给读协程,读协程获得数据后继续执行,此时若 sendq 非空,则从 sendq 中弹出一个写协程转入 running 状态,待写数据入队列 buf ,此时读取操作&nbsp;<code>&lt;- ch</code>&nbsp;未阻塞;</li>
<li>当 buf 为空但 sendq 非空时(不带缓冲的 channel),则从 sendq 中弹出一个写协程转入 running 状态,待写数据直接传递给读协程,读协程继续执行,此时读取操作&nbsp;<code>&lt;- ch</code>&nbsp;未阻塞;</li>
<li>当 buf 为空并且 sendq 也为空时,读协程入队列 recvq 并转入 blocking 状态,当后续有其他协程往 channel 写数据时,读协程才会重新转入 running 状态,此时读取操作&nbsp;<code>&lt;- ch</code>&nbsp;阻塞。</li>
</ul>
<p>  类似的,当协程尝试往未关闭的 channel 中写入数据时,内部的操作如下:</p>
<ul>
<li>当队列 recvq 非空时,此时队列 buf 必为空,从 recvq 弹出一个读协程接收待写数据,此读协程此时结束阻塞并转入 running 状态,写协程继续执行,此时写入操作&nbsp;<code>ch &lt;-</code>&nbsp;未阻塞;</li>
<li>当队列 recvq 为空但 buf 未满时,此时 sendq 必为空,写协程的待写数据入 buf 然后继续执行,此时写入操作&nbsp;<code>ch &lt;-</code>&nbsp;未阻塞;</li>
<li>当队列 recvq 为空并且 buf 为满时,此时写协程入队列 sendq 并转入 blokcing 状态,当后续有其他协程从 channel 中读数据时,写协程才会重新转入 running 状态,此时写入操作&nbsp;<code>ch &lt;-</code>&nbsp;阻塞。</li>
</ul>
<p>  当关闭 non-nil channel 时,内部的操作如下:</p>
<ul>
<li>当队列 recvq 非空时,此时 buf 必为空,recvq 中的所有协程都将收到对应类型的零值然后结束阻塞状态;</li>
<li>当队列 sendq 非空时,此时 buf 必为满,sendq 中的所有协程都会产生 panic ,在 buf 中数据仍然会保留直到被其他协程读取。</li>
</ul>
<p>  空通道是指将一个channel赋值为nil,或者定义后不调用make进行初始化。按照Go语言的语言规范,读写空通道是永远阻塞的。其实在函数runtime.chansend和runtime.chanrecv开头就有判断这类情况,如果发现参数c是空的,则直接将当前的goroutine放到等待队列,状态设置为waiting。</p>
<p>  读一个关闭的通道,永远不会阻塞,会返回一个通道数据类型的零值。这个实现也很简单,将零值复制到调用函数的参数ep中。写一个关闭的通道,则会panic。关闭一个空通道,也会导致panic。</p>
<h3>3. channel的高级用法</h3>
<h4>3.1 条件变量(condition variable)</h4>
<p>  类型于 POSIX 接口中线程通知其他线程某个事件发生的条件变量,channel 的特性也可以用来当成协程之间同步的条件变量。因为 channel 只是用来通知,所以 channel 中具体的数据类型和值并不重要,这种场景一般用&nbsp;<code>struct {}</code>&nbsp;作为 channel 的类型。</p>
<h5 id="一对一通知">一对一通知</h5>
<p>  类似&nbsp;<code>pthread_cond_signal()</code>&nbsp;的功能,用来在一个协程中通知另个某一个协程事件发生:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)">import (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fmt</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">time</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>   ch := make(chan <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{})
</span><span style="color: rgba(0, 128, 128, 1)">10</span>   nums := make([]<span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(128, 0, 128, 1)">100</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">11</span>
<span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)">    go func() {
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)">      time.Sleep(time.Second)
</span><span style="color: rgba(0, 128, 128, 1)">14</span>         <span style="color: rgba(0, 0, 255, 1)">for</span> i := <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; len(nums); i++<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">15</span>             nums =<span style="color: rgba(0, 0, 0, 1)"> i
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 0, 1)">      }
</span><span style="color: rgba(0, 128, 128, 1)">17</span>         <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> send a finish signal</span>
<span style="color: rgba(0, 128, 128, 1)">18</span>         ch &lt;- <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{}{}
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 0, 1)">    }()
</span><span style="color: rgba(0, 128, 128, 1)">20</span>
<span style="color: rgba(0, 128, 128, 1)">21</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> wait for finish signal</span>
<span style="color: rgba(0, 128, 128, 1)">22</span>   &lt;-<span style="color: rgba(0, 0, 0, 1)">ch
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)">    fmt.Println(nums)
</span><span style="color: rgba(0, 128, 128, 1)">24</span> }</pre>
</div>
<h5 id="广播通知">广播通知</h5>
<p>  类似&nbsp;<code>pthread_cond_broadcast()</code>&nbsp;的功能。利用从已关闭的 channel 读取数据时总是非阻塞的特性,可以实现在一个协程中向其他多个协程广播某个事件发生的通知:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)">import (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fmt</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">time</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>   N := <span style="color: rgba(128, 0, 128, 1)">10</span>
<span style="color: rgba(0, 128, 128, 1)">10</span>   exit := make(chan <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{})
</span><span style="color: rgba(0, 128, 128, 1)">11</span>   done := make(chan <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{}, N)
</span><span style="color: rgba(0, 128, 128, 1)">12</span>
<span style="color: rgba(0, 128, 128, 1)">13</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> start N worker goroutines</span>
<span style="color: rgba(0, 128, 128, 1)">14</span>   <span style="color: rgba(0, 0, 255, 1)">for</span> i := <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; N; i++<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">15</span>         go func(n <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">16</span>             <span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">17</span>               <span style="color: rgba(0, 0, 255, 1)">select</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">18</span>               <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> wait for exit signal</span>
<span style="color: rgba(0, 128, 128, 1)">19</span>               <span style="color: rgba(0, 0, 255, 1)">case</span> &lt;-<span style="color: rgba(0, 0, 0, 1)">exit:
</span><span style="color: rgba(0, 128, 128, 1)">20</span>                     fmt.Printf(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">worker goroutine #%d exit\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, n)
</span><span style="color: rgba(0, 128, 128, 1)">21</span>                     done &lt;- <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{}{}
</span><span style="color: rgba(0, 128, 128, 1)">22</span>                     <span style="color: rgba(0, 0, 255, 1)">return</span>
<span style="color: rgba(0, 128, 128, 1)">23</span>               <span style="color: rgba(0, 0, 255, 1)">case</span> &lt;-<span style="color: rgba(0, 0, 0, 1)">time.After(time.Second):
</span><span style="color: rgba(0, 128, 128, 1)">24</span>                     fmt.Printf(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">worker goroutine #%d is working...\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, n)
</span><span style="color: rgba(0, 128, 128, 1)">25</span> <span style="color: rgba(0, 0, 0, 1)">                }
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 0, 1)">      }(i)
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">29</span>
<span style="color: rgba(0, 128, 128, 1)">30</span>   time.Sleep(<span style="color: rgba(128, 0, 128, 1)">3</span> *<span style="color: rgba(0, 0, 0, 1)"> time.Second)
</span><span style="color: rgba(0, 128, 128, 1)">31</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> broadcast exit signal</span>
<span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)">    close(exit)
</span><span style="color: rgba(0, 128, 128, 1)">33</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> wait for all worker goroutines exit</span>
<span style="color: rgba(0, 128, 128, 1)">34</span>   <span style="color: rgba(0, 0, 255, 1)">for</span> i := <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; N; i++<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">35</span>         &lt;-<span style="color: rgba(0, 0, 0, 1)">done
</span><span style="color: rgba(0, 128, 128, 1)">36</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">37</span>   fmt.Println(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">main goroutine exit</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">38</span> }</pre>
</div>
<h4 id="信号量">3.2 信号量</h4>
<p>  channel 的读/写相当于信号量的 P / V 操作,下面的示例程序中 channel 相当于信号量:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)">import (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">log</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">math/rand</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">time</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span> type Seat <span style="color: rgba(0, 0, 255, 1)">int</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 0, 1)">type Bar chan Seat
</span><span style="color: rgba(0, 128, 128, 1)">11</span>
<span style="color: rgba(0, 128, 128, 1)">12</span> func (bar Bar) ServeConsumer(customerId <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">13</span>   log.Print(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-&gt; consumer#</span><span style="color: rgba(128, 0, 0, 1)">"</span>, customerId, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> enters the bar</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">14</span>   seat := &lt;-bar <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> need a seat to drink</span>
<span style="color: rgba(0, 128, 128, 1)">15</span>   log.Print(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">consumer#</span><span style="color: rgba(128, 0, 0, 1)">"</span>, customerId, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> drinks at seat#</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, seat)
</span><span style="color: rgba(0, 128, 128, 1)">16</span>   time.Sleep(time.Second * time.Duration(<span style="color: rgba(128, 0, 128, 1)">2</span>+rand.Intn(<span style="color: rgba(128, 0, 128, 1)">6</span><span style="color: rgba(0, 0, 0, 1)">)))
</span><span style="color: rgba(0, 128, 128, 1)">17</span>   log.Print(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;- consumer#</span><span style="color: rgba(128, 0, 0, 1)">"</span>, customerId, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> frees seat#</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, seat)
</span><span style="color: rgba(0, 128, 128, 1)">18</span>   bar &lt;- seat <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> free the seat and leave the bar</span>
<span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">20</span>
<span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 0, 0, 1)">    rand.Seed(time.Now().UnixNano())
</span><span style="color: rgba(0, 128, 128, 1)">23</span>
<span style="color: rgba(0, 128, 128, 1)">24</span>   bar24x7 := make(Bar, <span style="color: rgba(128, 0, 128, 1)">10</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> the bar has 10 seats
</span><span style="color: rgba(0, 128, 128, 1)">25</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Place seats in an bar.</span>
<span style="color: rgba(0, 128, 128, 1)">26</span>   <span style="color: rgba(0, 0, 255, 1)">for</span> seatId := <span style="color: rgba(128, 0, 128, 1)">0</span>; seatId &lt; cap(bar24x7); seatId++<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">27</span>         bar24x7 &lt;- Seat(seatId) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> none of the sends will block</span>
<span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">29</span>
<span style="color: rgba(0, 128, 128, 1)">30</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> a new consumer try to enter the bar for each second</span>
<span style="color: rgba(0, 128, 128, 1)">31</span>   <span style="color: rgba(0, 0, 255, 1)">for</span> customerId := <span style="color: rgba(128, 0, 128, 1)">0</span>; ; customerId++<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)">      time.Sleep(time.Second)
</span><span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 0, 1)">      go bar24x7.ServeConsumer(customerId)
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">35</span> }</pre>
</div>
<h4 id="互斥量">3.3 互斥量</h4>
<p>  互斥量相当于二元信号里,所以 cap 为 1 的 channel 可以当成互斥量使用:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> import <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fmt</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>   mutex := make(chan <span style="color: rgba(0, 0, 255, 1)">struct</span>{}, <span style="color: rgba(128, 0, 128, 1)">1</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> the capacity must be one</span>
<span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span>   counter := <span style="color: rgba(128, 0, 128, 1)">0</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span>   increase :=<span style="color: rgba(0, 0, 0, 1)"> func() {
</span><span style="color: rgba(0, 128, 128, 1)">10</span>         mutex &lt;- <span style="color: rgba(0, 0, 255, 1)">struct</span>{}{} <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> lock</span>
<span style="color: rgba(0, 128, 128, 1)">11</span>         counter++
<span style="color: rgba(0, 128, 128, 1)">12</span>         &lt;-mutex <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> unlock</span>
<span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">14</span>
<span style="color: rgba(0, 128, 128, 1)">15</span>   increase1000 := func(done chan&lt;- <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{}) {
</span><span style="color: rgba(0, 128, 128, 1)">16</span>         <span style="color: rgba(0, 0, 255, 1)">for</span> i := <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; <span style="color: rgba(128, 0, 128, 1)">1000</span>; i++<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)">            increase()
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)">      }
</span><span style="color: rgba(0, 128, 128, 1)">19</span>         done &lt;- <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{}{}
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">21</span>
<span style="color: rgba(0, 128, 128, 1)">22</span>   done := make(chan <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{})
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)">    go increase1000(done)
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 0, 1)">    go increase1000(done)
</span><span style="color: rgba(0, 128, 128, 1)">25</span>   &lt;-done; &lt;-<span style="color: rgba(0, 0, 0, 1)">done
</span><span style="color: rgba(0, 128, 128, 1)">26</span>   fmt.Println(counter) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2000</span>
<span style="color: rgba(0, 128, 128, 1)">27</span> }</pre>
</div>
<h3 id="关闭-channel">4. 关闭 channel</h3>
<p>  关闭不再需要使用的 channel 并不是必须的。跟其他资源比如打开的文件、socket 连接不一样,这类资源使用完后不关闭后会造成句柄泄露,channel 使用完后不关闭也没有关系,channel 没有被任何协程用到后最终会被 GC 回收。关闭 channel 一般是用来通知其他协程某个任务已经完成了。golang 也没有直接提供判断 channel 是否已经关闭的接口,虽然可以用其他不太优雅的方式自己实现一个:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> func isClosed(ch chan <span style="color: rgba(0, 0, 255, 1)">int</span>) <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">2</span>   <span style="color: rgba(0, 0, 255, 1)">select</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">3</span>   <span style="color: rgba(0, 0, 255, 1)">case</span> &lt;-<span style="color: rgba(0, 0, 0, 1)">ch:
</span><span style="color: rgba(0, 128, 128, 1)">4</span>         <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span>
<span style="color: rgba(0, 128, 128, 1)">5</span>   <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 128, 128, 1)">6</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">7</span>   <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span>
<span style="color: rgba(0, 128, 128, 1)">8</span> }</pre>
</div>
<p>  不过实现一个这样的接口也没什么必要。因为就算通过&nbsp;<code>isClosed()</code>&nbsp;得到当前 channel 当前还未关闭,如果试图往 channel 里写数据,仍然可能会发生 panic ,因为在调用&nbsp;<code>isClosed()</code>&nbsp;后,其他协程可能已经把 channel 关闭了。<br>关闭 channel 时应该注意以下准则:</p>
<ul>
<li>不要在读取端关闭 channel ,因为写入端无法知道 channel 是否已经关闭,往已关闭的 channel 写数据会 panic ;</li>
<li>有多个写入端时,不要再写入端关闭 channle ,因为其他写入端无法知道 channel 是否已经关闭,关闭已经关闭的 channel 会发生 panic ;</li>
<li>如果只有一个写入端,可以在这个写入端放心关闭 channel 。</li>







</ul>
<p>  关闭 channel 粗暴一点的做法是随意关闭,如果产生了 panic 就用 recover 避免进程挂掉。稍好一点的方案是使用标准库的&nbsp;<code>sync</code>&nbsp;包来做关闭 channel 时的协程同步,不过使用起来也稍微复杂些。下面介绍一种优雅些的做法。</p>
<h4 id="一写多读">4.1 一写多读</h4>
<p>  这种场景下这个唯一的写入端可以关闭 channel 用来通知读取端所有数据都已经写入完成了。读取端只需要用&nbsp;<code>for range</code>&nbsp;把 channel 中数据遍历完就可以了,当 channel 关闭时,<code>for range</code>&nbsp;仍然会将 channel 缓冲中的数据全部遍历完然后再退出循环:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)">import (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fmt</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sync</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>   wg := &amp;<span style="color: rgba(0, 0, 0, 1)">sync.WaitGroup{}
</span><span style="color: rgba(0, 128, 128, 1)">10</span>   ch := make(chan <span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(128, 0, 128, 1)">100</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">11</span>
<span style="color: rgba(0, 128, 128, 1)">12</span>   send :=<span style="color: rgba(0, 0, 0, 1)"> func() {
</span><span style="color: rgba(0, 128, 128, 1)">13</span>         <span style="color: rgba(0, 0, 255, 1)">for</span> i := <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; <span style="color: rgba(128, 0, 128, 1)">100</span>; i++<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">14</span>             ch &lt;-<span style="color: rgba(0, 0, 0, 1)"> i
</span><span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 0, 0, 1)">      }
</span><span style="color: rgba(0, 128, 128, 1)">16</span>         <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> signal sending finish</span>
<span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)">      close(ch)
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">19</span>
<span style="color: rgba(0, 128, 128, 1)">20</span>   recv := func(id <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(0, 0, 0, 1)">      defer wg.Done()
</span><span style="color: rgba(0, 128, 128, 1)">22</span>         <span style="color: rgba(0, 0, 255, 1)">for</span> i :=<span style="color: rgba(0, 0, 0, 1)"> range ch {
</span><span style="color: rgba(0, 128, 128, 1)">23</span>             fmt.Printf(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">receiver #%d get %d\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id, i)
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 0, 1)">      }
</span><span style="color: rgba(0, 128, 128, 1)">25</span>         fmt.Printf(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">receiver #%d exit\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id)
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">27</span>
<span style="color: rgba(0, 128, 128, 1)">28</span>   wg.Add(<span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">29</span>   go recv(<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">30</span>   go recv(<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">31</span>   go recv(<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)">    send()
</span><span style="color: rgba(0, 128, 128, 1)">33</span>
<span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 0, 1)">    wg.Wait()
</span><span style="color: rgba(0, 128, 128, 1)">35</span> }</pre>
</div>
<h4 id="多写一读">4.2 多写一读</h4>
<p>  这种场景下虽然可以用&nbsp;<code>sync.Once</code>&nbsp;来解决多个写入端重复关闭 channel 的问题,但更优雅的办法设置一个额外的 channel ,由读取端通过关闭来通知写入端任务完成不要再继续再写入数据了:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)">import (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fmt</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sync</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>   wg := &amp;<span style="color: rgba(0, 0, 0, 1)">sync.WaitGroup{}
</span><span style="color: rgba(0, 128, 128, 1)">10</span>   ch := make(chan <span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(128, 0, 128, 1)">100</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">11</span>   done := make(chan <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{})
</span><span style="color: rgba(0, 128, 128, 1)">12</span>
<span style="color: rgba(0, 128, 128, 1)">13</span>   send := func(id <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)">      defer wg.Done()
</span><span style="color: rgba(0, 128, 128, 1)">15</span>         <span style="color: rgba(0, 0, 255, 1)">for</span> i := <span style="color: rgba(128, 0, 128, 1)">0</span>; ; i++<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">16</span>             <span style="color: rgba(0, 0, 255, 1)">select</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">17</span>             <span style="color: rgba(0, 0, 255, 1)">case</span> &lt;-<span style="color: rgba(0, 0, 0, 1)">done:
</span><span style="color: rgba(0, 128, 128, 1)">18</span>               <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> get exit signal</span>
<span style="color: rgba(0, 128, 128, 1)">19</span>               fmt.Printf(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sender #%d exit\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id)
</span><span style="color: rgba(0, 128, 128, 1)">20</span>               <span style="color: rgba(0, 0, 255, 1)">return</span>
<span style="color: rgba(0, 128, 128, 1)">21</span>             <span style="color: rgba(0, 0, 255, 1)">case</span> ch &lt;- id*<span style="color: rgba(128, 0, 128, 1)">1000</span> +<span style="color: rgba(0, 0, 0, 1)"> i:
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)">      }
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">25</span>
<span style="color: rgba(0, 128, 128, 1)">26</span>   recv :=<span style="color: rgba(0, 0, 0, 1)"> func() {
</span><span style="color: rgba(0, 128, 128, 1)">27</span>         count := <span style="color: rgba(128, 0, 128, 1)">0</span>
<span style="color: rgba(0, 128, 128, 1)">28</span>         <span style="color: rgba(0, 0, 255, 1)">for</span> i :=<span style="color: rgba(0, 0, 0, 1)"> range ch {
</span><span style="color: rgba(0, 128, 128, 1)">29</span>             fmt.Printf(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">receiver get %d\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, i)
</span><span style="color: rgba(0, 128, 128, 1)">30</span>             count++
<span style="color: rgba(0, 128, 128, 1)">31</span>             <span style="color: rgba(0, 0, 255, 1)">if</span> count &gt;= <span style="color: rgba(128, 0, 128, 1)">1000</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">32</span>               <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> signal recving finish</span>
<span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 0, 1)">                close(done)
</span><span style="color: rgba(0, 128, 128, 1)">34</span>               <span style="color: rgba(0, 0, 255, 1)">return</span>
<span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">36</span> <span style="color: rgba(0, 0, 0, 1)">      }
</span><span style="color: rgba(0, 128, 128, 1)">37</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">38</span>
<span style="color: rgba(0, 128, 128, 1)">39</span>   wg.Add(<span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">40</span>   go send(<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">41</span>   go send(<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">42</span>   go send(<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">43</span> <span style="color: rgba(0, 0, 0, 1)">    recv()
</span><span style="color: rgba(0, 128, 128, 1)">44</span>
<span style="color: rgba(0, 128, 128, 1)">45</span> <span style="color: rgba(0, 0, 0, 1)">    wg.Wait()
</span><span style="color: rgba(0, 128, 128, 1)">46</span> }</pre>
</div>
<h4 id="多写多读">4.2 多写多读</h4>
<p>  这种场景稍微复杂,和上面的例子一样,也需要设置一个额外 channel 用来通知多个写入端和读取端。另外需要起一个额外的协程来通过关闭这个 channel 来广播通知:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)">import (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fmt</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sync</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">time</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)">10</span>   wg := &amp;<span style="color: rgba(0, 0, 0, 1)">sync.WaitGroup{}
</span><span style="color: rgba(0, 128, 128, 1)">11</span>   ch := make(chan <span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(128, 0, 128, 1)">100</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">12</span>   done := make(chan <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)">{})
</span><span style="color: rgba(0, 128, 128, 1)">13</span>
<span style="color: rgba(0, 128, 128, 1)">14</span>   send := func(id <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 0, 0, 1)">      defer wg.Done()
</span><span style="color: rgba(0, 128, 128, 1)">16</span>         <span style="color: rgba(0, 0, 255, 1)">for</span> i := <span style="color: rgba(128, 0, 128, 1)">0</span>; ; i++<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">17</span>             <span style="color: rgba(0, 0, 255, 1)">select</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">18</span>             <span style="color: rgba(0, 0, 255, 1)">case</span> &lt;-<span style="color: rgba(0, 0, 0, 1)">done:
</span><span style="color: rgba(0, 128, 128, 1)">19</span>               <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> get exit signal</span>
<span style="color: rgba(0, 128, 128, 1)">20</span>               fmt.Printf(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sender #%d exit\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id)
</span><span style="color: rgba(0, 128, 128, 1)">21</span>               <span style="color: rgba(0, 0, 255, 1)">return</span>
<span style="color: rgba(0, 128, 128, 1)">22</span>             <span style="color: rgba(0, 0, 255, 1)">case</span> ch &lt;- id*<span style="color: rgba(128, 0, 128, 1)">1000</span> +<span style="color: rgba(0, 0, 0, 1)"> i:
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 0, 1)">      }
</span><span style="color: rgba(0, 128, 128, 1)">25</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">26</span>
<span style="color: rgba(0, 128, 128, 1)">27</span>   recv := func(id <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)">      defer wg.Done()
</span><span style="color: rgba(0, 128, 128, 1)">29</span>         <span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">30</span>             <span style="color: rgba(0, 0, 255, 1)">select</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">31</span>             <span style="color: rgba(0, 0, 255, 1)">case</span> &lt;-<span style="color: rgba(0, 0, 0, 1)">done:
</span><span style="color: rgba(0, 128, 128, 1)">32</span>               <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> get exit signal</span>
<span style="color: rgba(0, 128, 128, 1)">33</span>               fmt.Printf(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">receiver #%d exit\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id)
</span><span style="color: rgba(0, 128, 128, 1)">34</span>               <span style="color: rgba(0, 0, 255, 1)">return</span>
<span style="color: rgba(0, 128, 128, 1)">35</span>             <span style="color: rgba(0, 0, 255, 1)">case</span> i := &lt;-<span style="color: rgba(0, 0, 0, 1)">ch:
</span><span style="color: rgba(0, 128, 128, 1)">36</span>               fmt.Printf(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">receiver #%d get %d\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, id, i)
</span><span style="color: rgba(0, 128, 128, 1)">37</span> <span style="color: rgba(0, 0, 0, 1)">                time.Sleep(time.Millisecond)
</span><span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 0, 1)">            }
</span><span style="color: rgba(0, 128, 128, 1)">39</span> <span style="color: rgba(0, 0, 0, 1)">      }
</span><span style="color: rgba(0, 128, 128, 1)">40</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">41</span>
<span style="color: rgba(0, 128, 128, 1)">42</span>   wg.Add(<span style="color: rgba(128, 0, 128, 1)">6</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">43</span>   go send(<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">44</span>   go send(<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">45</span>   go send(<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">46</span>   go recv(<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">47</span>   go recv(<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">48</span>   go recv(<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">49</span>
<span style="color: rgba(0, 128, 128, 1)">50</span> <span style="color: rgba(0, 0, 0, 1)">    time.Sleep(time.Second)
</span><span style="color: rgba(0, 128, 128, 1)">51</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> signal finish</span>
<span style="color: rgba(0, 128, 128, 1)">52</span> <span style="color: rgba(0, 0, 0, 1)">    close(done)
</span><span style="color: rgba(0, 128, 128, 1)">53</span>   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> wait all sender and receiver exit</span>
<span style="color: rgba(0, 128, 128, 1)">54</span> <span style="color: rgba(0, 0, 0, 1)">    wg.Wait()
</span><span style="color: rgba(0, 128, 128, 1)">55</span> }</pre>
</div>
<p>  channle 作为 golang 最重要的特性,用起来还是比较方便的。传统的 C 里要实现类似的功能的话,一般需要用到 socket 或者 FIFO 来实现,另外还要考虑数据包的完整性与并发冲突的问题,channel 则屏蔽了这些底层细节,使用者只需要考虑读写就可以了。 channel 是引用类型,了解一下 channel 底层的机制对更好的使用 channel 还是很用必要的。虽然操作原语简单,但涉及到阻塞的问题,使用不当可能会造成死锁或者无限制的协程创建最终导致进程挂掉。</p><br><br>
来源:https://www.cnblogs.com/33debug/p/11889825.html
頁: [1]
查看完整版本: go中通道channel的使用及原理