教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang教程:goroutine信道

Golang教程:goroutine信道

发布时间:2022-03-15   编辑:jiaochengji.com
教程集为您提供Golang教程:goroutine信道等资源,欢迎您收藏本站,我们将为您提供最新的Golang教程:goroutine信道资源

在上一篇教程中,我们讨论了如何使用协程实现并发。在这篇教程中,我们将讨论信道以及如何使用信道实现协程间通信。

<h1 id="什么是信道">什么是信道</h1>

信道(Channel)可以被认为是协程之间通信的管道。与水流从管道的一端流向另一端一样,数据可以从信道的一端发送并在另一端接收。

<h1 id="声明信道">声明信道</h1>

每个信道都有一个与之关联的类型。此类型是允许信道传输的数据类型,除此类型外不能通过信道传输其他类型。

<code>chan T</code> 是一个 <code>T</code> 类型的信道。

信道的 0 值为 <code>nil</code>。值为 <code>nil</code> 的信道变量没有任何用处,我们需要通过内置函数 <code>make</code> 来创建一个信道,就像创建map和 slice一样。

下面的代码声明了一个信道:

<pre><span style="color: #008080"> 1</span> <span style="color: #000000">package main </span><span style="color: #008080"> 2</span> <span style="color: #008080"> 3</span> import <span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span> <span style="color: #008080"> 4</span> <span style="color: #008080"> 5</span> <span style="color: #000000">func main() { </span><span style="color: #008080"> 6</span> <span style="color: #0000ff">var</span> a chan <span style="color: #0000ff">int</span> <span style="color: #008080"> 7</span> <span style="color: #0000ff">if</span> a ==<span style="color: #000000"> nil { </span><span style="color: #008080"> 8</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">channel a is nil, going to define it</span><span style="color: #800000">"</span><span style="color: #000000">) </span><span style="color: #008080"> 9</span> a = make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) </span><span style="color: #008080">10</span> fmt.Printf(<span style="color: #800000">"</span><span style="color: #800000">Type of a is %T</span><span style="color: #800000">"</span><span style="color: #000000">, a) </span><span style="color: #008080">11</span> <span style="color: #000000"> } </span><span style="color: #008080">12</span> }</pre>

因为信道的 0 值为 <code>nil</code>,因此第 6 行声明的信道 <code>a</code> 的值为 <code>nil</code>。因此执行 <code>if</code> 里面的语句创建信道。上面的程序中 <code>a</code> 是一个 <code>int</code> 类型的信道。程序的输出为:

<pre>channel a <span style="color: #0000ff">is</span><span style="color: #000000"> nil, going to define it Type of a </span><span style="color: #0000ff">is</span> chan <span style="color: #0000ff">int</span> </pre>

像往常一样,速记声明也是定义信道的一种有效而简洁的方式:

<pre>a := make(chan <span style="color: #0000ff">int</span>) </pre>

上面的这行代码同样定义了一个 <code>int</code> 型的信道。

<h1 id="通过信道发送和接收数据">通过信道发送和接收数据</h1>

通过信道发送和接收数据的语法如下:

<pre>data := <- a <span style="color: #008000">//</span><span style="color: #008000"> read from channel a </span> a <- data <span style="color: #008000">//</span><span style="color: #008000"> write to channel a </span></pre>

箭头的指向说明了数据是发送还是接收。

在第一行,箭头的方向是从 <code>a</code> 向外指,因此我们正在从信道 <code>a</code> 中读取数据并将读取的值赋值给变量 <code>data</code> 。

在第二行,箭头的方式是指向 <code>a</code> ,因此我们正在向信道 <code>a</code> 中写入数据。

<h1 id="发送和接收默认是阻塞的">发送和接收默认是阻塞的</h1>

通过信道发送和接收数据默认是阻塞的。这是什么意思呢?当数据发送给信道后,程序流程在发送语句处阻塞,直到其他协程从该信道中读取数据。同样地,当从信道读取数据时,程序在读取语句处阻塞,直到其他协程发送数据给该信道。

信道的这种特性使得协程间通信变得高效,而不是向其他编程语言一样,显式的使用锁和条件变量来达到此目的。

<h1 id="信道的一个例子">信道的一个例子</h1>

理论到此为止:) 让我们通过一个程序来理解协程之间如何使用信道进行通信。

我们将用信道来重写在上一篇教程中的一个例子。

如下是那篇教程中的一个例子:

<pre><span style="color: #000000">package main import ( </span><span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span> <span style="color: #800000">"</span><span style="color: #800000">time</span><span style="color: #800000">"</span><span style="color: #000000"> ) func hello() { fmt.Println(</span><span style="color: #800000">"</span><span style="color: #800000">Hello world goroutine</span><span style="color: #800000">"</span><span style="color: #000000">) } func main() { go hello() time.Sleep(</span><span style="color: #800080">1</span> *<span style="color: #000000"> time.Second) fmt.Println(</span><span style="color: #800000">"</span><span style="color: #800000">main function</span><span style="color: #800000">"</span><span style="color: #000000">) }</span></pre>

这是上一篇教程中的例子,我们通过使用 <code>Sleep</code> 来使主协程休眠,以等待 <code>hello</code> 协程执行结束。如果你不明白这是为什么,请阅读上一篇教程

我们用信道重写上面的程序,如下:

<pre><span style="color: #008080"> 1</span> <span style="color: #000000">package main </span><span style="color: #008080"> 2</span> <span style="color: #008080"> 3</span> <span style="color: #000000">import ( </span><span style="color: #008080"> 4</span> <span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span> <span style="color: #008080"> 5</span> <span style="color: #000000">) </span><span style="color: #008080"> 6</span> <span style="color: #008080"> 7</span> func hello(done chan <span style="color: #0000ff">bool</span><span style="color: #000000">) { </span><span style="color: #008080"> 8</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">Hello world goroutine</span><span style="color: #800000">"</span><span style="color: #000000">) </span><span style="color: #008080"> 9</span> done <- <span style="color: #0000ff">true</span> <span style="color: #008080">10</span> <span style="color: #000000">} </span><span style="color: #008080">11</span> <span style="color: #000000">func main() { </span><span style="color: #008080">12</span> done := make(chan <span style="color: #0000ff">bool</span><span style="color: #000000">) </span><span style="color: #008080">13</span> <span style="color: #000000"> go hello(done) </span><span style="color: #008080">14</span> <-<span style="color: #000000">done </span><span style="color: #008080">15</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">main function</span><span style="color: #800000">"</span><span style="color: #000000">) </span><span style="color: #008080">16</span> }</pre>

在上面的程序中,我们在第 12 行定义了一个 bool 类型的信道 <code>done</code>,然后将它作为参数传递给 <code>hello</code> 协程。在第 14 行,我们从信道 <code>done</code> 中读取数据。程序将在这一行被阻塞直到其他协程向信道 <code>done</code> 里写入数据,在未读取到数据之前程序将在这一行一直等待而不会执行下一行语句。因此这里消除了在原程序中使用 <code>time.Sleep</code> 来阻止主协程退出的必要。

<code><-done</code> 这一行从信道 <code>done</code> 中读取数据,但是没有使用该数据,也没有将它赋值给其他变量,这是完全合法的。

现在我们的 <code>main</code> 协程被阻塞,等待从信道 <code>done</code> 中读取数据。<code>hello</code> 协程接受信道 <code>done</code> 作为参数,打印 <code>Hello world goroutine</code> 然后将数据写入信道 <code>done</code> 中。当写入完毕后,<code>main</code> 协程从信道 <code>done</code> 中接收到数据,<code>main</code> 协程解除阻塞,继续执行下一条语句,打印:<code>main function</code>。

程序的输出为:

<pre><span style="color: #000000">Hello world goroutine main function </span></pre>

让我们修改上面程序,在 <code>hello</code> 协程中加入一个休眠,来更好的理解阻塞的概念。

<pre><span style="color: #008080"> 1</span> <span style="color: #000000">package main </span><span style="color: #008080"> 2</span> <span style="color: #008080"> 3</span> <span style="color: #000000">import ( </span><span style="color: #008080"> 4</span> <span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span> <span style="color: #008080"> 5</span> <span style="color: #800000">"</span><span style="color: #800000">time</span><span style="color: #800000">"</span> <span style="color: #008080"> 6</span> <span style="color: #000000">) </span><span style="color: #008080"> 7</span> <span style="color: #008080"> 8</span> func hello(done chan <span style="color: #0000ff">bool</span><span style="color: #000000">) { </span><span style="color: #008080"> 9</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">hello go routine is going to sleep</span><span style="color: #800000">"</span><span style="color: #000000">) </span><span style="color: #008080">10</span> time.Sleep(<span style="color: #800080">4</span> *<span style="color: #000000"> time.Second) </span><span style="color: #008080">11</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">hello go routine awake and going to write to done</span><span style="color: #800000">"</span><span style="color: #000000">) </span><span style="color: #008080">12</span> done <- <span style="color: #0000ff">true</span> <span style="color: #008080">13</span> <span style="color: #000000">} </span><span style="color: #008080">14</span> <span style="color: #000000">func main() { </span><span style="color: #008080">15</span> done := make(chan <span style="color: #0000ff">bool</span><span style="color: #000000">) </span><span style="color: #008080">16</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">Main going to call hello go goroutine</span><span style="color: #800000">"</span><span style="color: #000000">) </span><span style="color: #008080">17</span> <span style="color: #000000"> go hello(done) </span><span style="color: #008080">18</span> <-<span style="color: #000000">done </span><span style="color: #008080">19</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">Main received data</span><span style="color: #800000">"</span><span style="color: #000000">) </span><span style="color: #008080">20</span> }</pre>

在上面程序中的第 10 行,我们在 <code>hello</code> 函数中增加了4 秒钟的休眠。

该程序首先打印 <code>Main going to call hello go goroutine</code> 。然后 <code>hello</code> 协程开始执行,它将打印 <code>hello go routine is going to sleep</code>,然后 <code>hello</code> 协程休眠 4 秒,在这期间, <code>main</code> 协程由于在等待从信道 <code>done</code> 中读取数据而始终阻塞(在<code><-done</code> 这一行)。4 秒中之后, <code>hello</code> 协程打印:<code>hello go routine awake and going to write to don</code>,接着 <code>main</code> 协程打印:<code>Main received data</code> 。

<h1 id="信道的另一个例子"><span id="anotherexampleforchannels">信道的另一个例子</span></h1>

让我们再写一个例子来更好的理解信道。该程序打印一个数字的每一位的平方和与立方和,并将平方和与立方和相加得出最后的结果。

例如,输入123 ,程序将做如下计算以得出最后结果:

squares = (1 * 1) (2 * 2) (3 * 3) 
cubes = (1 * 1 * 1) (2 * 2 * 2) (3 * 3 * 3) 
output = squares cubes = 49

我们将平方和的计算与立方和的计算分别放在一个协程中执行,最后在主协程中将它们的计算结果求和。

<pre><span style="color: #008080"> 1</span> <span style="color: #000000">package main </span><span style="color: #008080"> 2</span> <span style="color: #008080"> 3</span> <span style="color: #000000">import ( </span><span style="color: #008080"> 4</span> <span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span> <span style="color: #008080"> 5</span> <span style="color: #000000">) </span><span style="color: #008080"> 6</span> <span style="color: #008080"> 7</span> func calcSquares(number <span style="color: #0000ff">int</span>, squareop chan <span style="color: #0000ff">int</span><span style="color: #000000">) { </span><span style="color: #008080"> 8</span> sum := <span style="color: #800080">0</span> <span style="color: #008080"> 9</span> <span style="color: #0000ff">for</span> number != <span style="color: #800080">0</span><span style="color: #000000"> { </span><span style="color: #008080">10</span> digit := number % <span style="color: #800080">10</span> <span style="color: #008080">11</span> sum = digit *<span style="color: #000000"> digit </span><span style="color: #008080">12</span> number /= <span style="color: #800080">10</span> <span style="color: #008080">13</span> <span style="color: #000000"> } </span><span style="color: #008080">14</span> squareop <-<span style="color: #000000"> sum </span><span style="color: #008080">15</span> <span style="color: #000000">} </span><span style="color: #008080">16</span> <span style="color: #008080">17</span> func calcCubes(number <span style="color: #0000ff">int</span>, cubeop chan <span style="color: #0000ff">int</span><span style="color: #000000">) { </span><span style="color: #008080">18</span> sum := <span style="color: #800080">0</span> <span style="color: #008080">19</span> <span style="color: #0000ff">for</span> number != <span style="color: #800080">0</span><span style="color: #000000"> { </span><span style="color: #008080">20</span> digit := number % <span style="color: #800080">10</span> <span style="color: #008080">21</span> sum = digit * digit *<span style="color: #000000"> digit </span><span style="color: #008080">22</span> number /= <span style="color: #800080">10</span> <span style="color: #008080">23</span> <span style="color: #000000"> } </span><span style="color: #008080">24</span> cubeop <-<span style="color: #000000"> sum </span><span style="color: #008080">25</span> <span style="color: #000000">} </span><span style="color: #008080">26</span> <span style="color: #008080">27</span> <span style="color: #000000">func main() { </span><span style="color: #008080">28</span> number := <span style="color: #800080">589</span> <span style="color: #008080">29</span> sqrch := make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) </span><span style="color: #008080">30</span> cubech := make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) </span><span style="color: #008080">31</span> <span style="color: #000000"> go calcSquares(number, sqrch) </span><span style="color: #008080">32</span> <span style="color: #000000"> go calcCubes(number, cubech) </span><span style="color: #008080">33</span> squares, cubes := <-sqrch, <-<span style="color: #000000">cubech </span><span style="color: #008080">34</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">Final output</span><span style="color: #800000">"</span>, squares <span style="color: #000000"> cubes) </span><span style="color: #008080">35</span> }</pre>

在第 7 行,函数 <code>calcSquares</code> 计算 <code>number</code> 每一位的平方和,并将结果发送给信道 <code>squareop</code>。同样地,在第 17 行,函数<code>calcCubes</code> 计算 <code>number</code> 每一位的立方和,并将结果发送给信道 <code>cubeop</code>。

这两个函数接受不同的信道作为参数,并分别运行在各自的协程中(第31行和32行),最后将结果写入各自的信道。主协程在第 33 行同时等待这两个信道中的数据。一旦从这两个信道中接收到数据,它们分别被存放在变量 <code>squares</code> 和 <code>cubes</code>中,最后将它们的和打印出来。程序的输出为:

<pre>Final output <span style="color: #800080">1536</span> </pre>

 

<h1 id="死锁">死锁</h1>

使用信道是要考虑的一个重要因素是死锁(Deadlock)。如果一个协程发送数据给一个信道,而没有其他的协程从该信道中接收数据,那么程序在运行时会遇到死锁,并触发 panic 。

同样地,如果一个协程从一个信道中接收数据,而没有其他的协程向这个信道发送数据,那么程序同样造成死锁,触发 panic 。

<pre><span style="color: #000000">package main func main() { ch :</span>= make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) ch </span><- <span style="color: #800080">5</span><span style="color: #000000"> }</span></pre>

上面的程序中,创建了一个信道 <code>ch</code>,并通过 <code>ch <- 5</code> 向其中写入 <code>5</code> 。这个程序中没有其他协程从 <code>ch</code> 中接收数据,因此程序在运行时触发 panic,错误如下:

<pre>fatal error: all goroutines are asleep - deadlock!<span style="color: #000000"> goroutine </span><span style="color: #800080">1</span><span style="color: #000000"> [chan send]: main.main() </span>/tmp/sandbox249677995/main.go:<span style="color: #800080">6</span> <span style="color: #800080">0x80</span></pre>
<h1 id="单向信道">单向信道</h1>

目前我们讨论的信道都是双向信道,数据既可以发送到双向信道,也可以从双向信道中读取。同样也可以创建单向信道,即只能发送数据或只能接收数据的信道。

<pre><span style="color: #000000">package main import </span><span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span><span style="color: #000000"> func sendData(sendch chan</span><- <span style="color: #0000ff">int</span><span style="color: #000000">) { sendch </span><- <span style="color: #800080">10</span><span style="color: #000000"> } func main() { sendch :</span>= make(chan<- <span style="color: #0000ff">int</span><span style="color: #000000">) go sendData(sendch) fmt.Println(</span><-<span style="color: #000000">sendch) }</span></pre>

在上面程序中的第 10 行,我们创建了一个只写(send only)信道 <code>sendch</code> 。<code>chan<- int</code> 表示只能发送数据,因为箭头的方向指向 <code>chan</code>。在第 12 行,我们试图从一个只写信道中接收数据,这是非法的,程序将无法通过编译,编译器报错如下:

<pre>main.go:<span style="color: #800080">11</span>: invalid operation: <-sendch (receive <span style="color: #0000ff">from</span> send-only type chan<- <span style="color: #0000ff">int</span>)</pre>

一切都很好,但是如果无法读取,创建一个只写通道有什么用呢?

这就是信道转型的用途。可以将双向信道转换为只写或只读信道,但是反过来却不行。

<pre><span style="color: #000000">package main import </span><span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span><span style="color: #000000"> func sendData(sendch chan</span><- <span style="color: #0000ff">int</span><span style="color: #000000">) { sendch </span><- <span style="color: #800080">10</span><span style="color: #000000"> } func main() { chnl :</span>= make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) go sendData(chnl) fmt.Println(</span><-<span style="color: #000000">chnl) }</span></pre>

在上面程序中的第 10 行,创建了一个双向信道 <code>chnl</code>。在第 11 行它被作为参数传递给协程 <code>sendData</code>。第 5 行,<code>sendData</code> 函数通过形参 <code>sendch chan<- int</code> 将该信道转换成只写信道。因此在 <code>sendData</code> 中该信道为只写信道,而在主协程中该信道为双向信道。程序将打印:<code>10</code>。

<h1 id="关闭信道以及使用-range-for-遍历信道">关闭信道以及使用 range for 遍历信道</h1>

发送者可以关闭信道以通知接收者将不会再发送数据给信道。

在从信道接收数据时,接收者可以通过一个额外的变量来检测信道是否已经被关闭。

<pre>v, ok := <- ch</pre>

上面的语句中 <code>ok</code> 返回 true 表示成功的接收到了发送的数据,如果 <code>ok</code> 返回 false 则表示信道已经被关闭。从已关闭的信道中读取到的数据为信道类型的 0 值。比如从一个被关闭的 <code>int</code> 信道中读取数据,那么将得到 0 。

<pre><span style="color: #000000">package main import ( </span><span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span><span style="color: #000000"> ) func producer(chnl chan </span><span style="color: #0000ff">int</span><span style="color: #000000">) { </span><span style="color: #0000ff">for</span> i := <span style="color: #800080">0</span>; i < <span style="color: #800080">10</span>; i <span style="color: #000000"> { chnl </span><-<span style="color: #000000"> i } close(chnl) } func main() { ch :</span>= make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) go producer(ch) </span><span style="color: #0000ff">for</span><span style="color: #000000"> { v, ok :</span>= <-<span style="color: #000000">ch </span><span style="color: #0000ff">if</span> ok == <span style="color: #0000ff">false</span><span style="color: #000000"> { </span><span style="color: #0000ff">break</span><span style="color: #000000"> } fmt.Println(</span><span style="color: #800000">"</span><span style="color: #800000">Received </span><span style="color: #800000">"</span><span style="color: #000000">, v, ok) } }</span></pre>

上面的程序中,协程 <code>producer</code> 向信道 <code>chnl</code> 中写入 0 到 9 并关闭该信道。主协程在第 16 行进行无限 for 循环,并在循环中检测 <code>ok</code> 的值判断信道是否已经被关闭(第 18 行)。如果 <code>ok</code> 是 false 表示信道已经被关闭,则通过 <code>break</code> 退出循环。否则接收数据并打印 <code>ok</code> 的值。程序的输出为:

<pre>Received <span style="color: #800080">0</span> <span style="color: #0000ff">true</span><span style="color: #000000"> Received </span><span style="color: #800080">1</span> <span style="color: #0000ff">true</span><span style="color: #000000"> Received </span><span style="color: #800080">2</span> <span style="color: #0000ff">true</span><span style="color: #000000"> Received </span><span style="color: #800080">3</span> <span style="color: #0000ff">true</span><span style="color: #000000"> Received </span><span style="color: #800080">4</span> <span style="color: #0000ff">true</span><span style="color: #000000"> Received </span><span style="color: #800080">5</span> <span style="color: #0000ff">true</span><span style="color: #000000"> Received </span><span style="color: #800080">6</span> <span style="color: #0000ff">true</span><span style="color: #000000"> Received </span><span style="color: #800080">7</span> <span style="color: #0000ff">true</span><span style="color: #000000"> Received </span><span style="color: #800080">8</span> <span style="color: #0000ff">true</span><span style="color: #000000"> Received </span><span style="color: #800080">9</span> <span style="color: #0000ff">true</span></pre>

range for 可以用来接收一个信道中的数据,直到该信道关闭。

让我们用 range for 重写上面的程序:

<pre><span style="color: #008080"> 1</span> <span style="color: #000000">package main </span><span style="color: #008080"> 2</span> <span style="color: #008080"> 3</span> <span style="color: #000000">import ( </span><span style="color: #008080"> 4</span> <span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span> <span style="color: #008080"> 5</span> <span style="color: #000000">) </span><span style="color: #008080"> 6</span> <span style="color: #008080"> 7</span> func producer(chnl chan <span style="color: #0000ff">int</span><span style="color: #000000">) { </span><span style="color: #008080"> 8</span> <span style="color: #0000ff">for</span> i := <span style="color: #800080">0</span>; i < <span style="color: #800080">10</span>; i <span style="color: #000000"> { </span><span style="color: #008080"> 9</span> chnl <-<span style="color: #000000"> i </span><span style="color: #008080">10</span> <span style="color: #000000"> } </span><span style="color: #008080">11</span> <span style="color: #000000"> close(chnl) </span><span style="color: #008080">12</span> <span style="color: #000000">} </span><span style="color: #008080">13</span> <span style="color: #000000">func main() { </span><span style="color: #008080">14</span> ch := make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) </span><span style="color: #008080">15</span> <span style="color: #000000"> go producer(ch) </span><span style="color: #008080">16</span> <span style="color: #0000ff">for</span> v :=<span style="color: #000000"> range ch { </span><span style="color: #008080">17</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">Received </span><span style="color: #800000">"</span><span style="color: #000000">,v) </span><span style="color: #008080">18</span> <span style="color: #000000"> } </span><span style="color: #008080">19</span> }</pre>

在第6 行,<code>for range</code> 不断从信道 <code>ch</code> 中接收数据,直到该信道关闭。一旦 <code>ch</code> 关闭,循环自动退出。程序的输出如下

<pre>Received <span style="color: #800080">0</span><span style="color: #000000"> Received </span><span style="color: #800080">1</span><span style="color: #000000"> Received </span><span style="color: #800080">2</span><span style="color: #000000"> Received </span><span style="color: #800080">3</span><span style="color: #000000"> Received </span><span style="color: #800080">4</span><span style="color: #000000"> Received </span><span style="color: #800080">5</span><span style="color: #000000"> Received </span><span style="color: #800080">6</span><span style="color: #000000"> Received </span><span style="color: #800080">7</span><span style="color: #000000"> Received </span><span style="color: #800080">8</span><span style="color: #000000"> Received </span><span style="color: #800080">9</span> </pre>

如果仔细观察该程序,你可以注意到,在 <code>calcSquares</code> 和 <code>calcCubes</code> 函数中查找一个数的每一位的代码重复了。我们将这段代码提取到一个单独的函数,并异步调用它。

<pre><span style="color: #008080"> 1</span> <span style="color: #000000">package main </span><span style="color: #008080"> 2</span> <span style="color: #008080"> 3</span> <span style="color: #000000">import ( </span><span style="color: #008080"> 4</span> <span style="color: #800000">"</span><span style="color: #800000">fmt</span><span style="color: #800000">"</span> <span style="color: #008080"> 5</span> <span style="color: #000000">) </span><span style="color: #008080"> 6</span> <span style="color: #008080"> 7</span> func digits(number <span style="color: #0000ff">int</span>, dchnl chan <span style="color: #0000ff">int</span><span style="color: #000000">) { </span><span style="color: #008080"> 8</span> <span style="color: #0000ff">for</span> number != <span style="color: #800080">0</span><span style="color: #000000"> { </span><span style="color: #008080"> 9</span> digit := number % <span style="color: #800080">10</span> <span style="color: #008080">10</span> dchnl <-<span style="color: #000000"> digit </span><span style="color: #008080">11</span> number /= <span style="color: #800080">10</span> <span style="color: #008080">12</span> <span style="color: #000000"> } </span><span style="color: #008080">13</span> <span style="color: #000000"> close(dchnl) </span><span style="color: #008080">14</span> <span style="color: #000000">} </span><span style="color: #008080">15</span> func calcSquares(number <span style="color: #0000ff">int</span>, squareop chan <span style="color: #0000ff">int</span><span style="color: #000000">) { </span><span style="color: #008080">16</span> sum := <span style="color: #800080">0</span> <span style="color: #008080">17</span> dch := make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) </span><span style="color: #008080">18</span> <span style="color: #000000"> go digits(number, dch) </span><span style="color: #008080">19</span> <span style="color: #0000ff">for</span> digit :=<span style="color: #000000"> range dch { </span><span style="color: #008080">20</span> sum = digit *<span style="color: #000000"> digit </span><span style="color: #008080">21</span> <span style="color: #000000"> } </span><span style="color: #008080">22</span> squareop <-<span style="color: #000000"> sum </span><span style="color: #008080">23</span> <span style="color: #000000">} </span><span style="color: #008080">24</span> <span style="color: #008080">25</span> func calcCubes(number <span style="color: #0000ff">int</span>, cubeop chan <span style="color: #0000ff">int</span><span style="color: #000000">) { </span><span style="color: #008080">26</span> sum := <span style="color: #800080">0</span> <span style="color: #008080">27</span> dch := make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) </span><span style="color: #008080">28</span> <span style="color: #000000"> go digits(number, dch) </span><span style="color: #008080">29</span> <span style="color: #0000ff">for</span> digit :=<span style="color: #000000"> range dch { </span><span style="color: #008080">30</span> sum = digit * digit *<span style="color: #000000"> digit </span><span style="color: #008080">31</span> <span style="color: #000000"> } </span><span style="color: #008080">32</span> cubeop <-<span style="color: #000000"> sum </span><span style="color: #008080">33</span> <span style="color: #000000">} </span><span style="color: #008080">34</span> <span style="color: #008080">35</span> <span style="color: #000000">func main() { </span><span style="color: #008080">36</span> number := <span style="color: #800080">589</span> <span style="color: #008080">37</span> sqrch := make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) </span><span style="color: #008080">38</span> cubech := make(chan <span style="color: #0000ff">int</span><span style="color: #000000">) </span><span style="color: #008080">39</span> <span style="color: #000000"> go calcSquares(number, sqrch) </span><span style="color: #008080">40</span> <span style="color: #000000"> go calcCubes(number, cubech) </span><span style="color: #008080">41</span> squares, cubes := <-sqrch, <-<span style="color: #000000">cubech </span><span style="color: #008080">42</span> fmt.Println(<span style="color: #800000">"</span><span style="color: #800000">Final output</span><span style="color: #800000">"</span>, squares <span style="color: #000000">cubes) </span><span style="color: #008080">43</span> }</pre>

在上面的程序中,函数 <code>digits</code> 包含查找一个数的每一位的逻辑,该函数在 <code>calcSquares</code> 和 <code>calcCubes</code> 中异步调用。在第 13 行,当数字中没有更多的位数时,信道被关闭。<code>calcSquares</code> 和 <code>calcCubes</code> 分别在各自的信道上使用 range for 直到信道被关闭。程序中的其他部分都是一样的。程序的输出依然是:

<pre>Final output <span style="color: #800080">1536</span> </pre>

这就来到了本教程的最后。信道中还有更多的概念,比如缓冲信道,工作池和 select 。

 

 

 

 

 

本文转自:https://blog.csdn.net/u011304970/article/details/76168257


到此这篇关于“Golang教程:goroutine信道”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
Golang并发编程中控制主goroutine退出实现的例子
Go:Goroutine 的切换过程实际上涉及了什么
[Go 教程系列笔记] goroutine(协程)
golang的channel机制
从Deadlock报错理解Go channel机制(一)
Go WaitGroup实现原理
golang 深入浅出之 goroutine 理解
goroutine 调度器
Golang中Goroutine与线程
简单理解 Goroutine 是如何工作的

[关闭]
~ ~