新聞中心
如果說 goroutine 是 Go語言程序的并發(fā)體的話,那么 channels 就是它們之間的通信機制。一個 channels 是一個通信機制,它可以讓一個 goroutine 通過它給另一個 goroutine 發(fā)送值信息。每個 channel 都有一個特殊的類型,也就是 channels 可發(fā)送數(shù)據(jù)的類型。一個可以發(fā)送 int 類型數(shù)據(jù)的 channel 一般寫為 chan int。

10年的亞東網(wǎng)站建設經(jīng)驗,針對設計、前端、開發(fā)、售后、文案、推廣等六對一服務,響應快,48小時及時工作處理。全網(wǎng)整合營銷推廣的優(yōu)勢是能夠根據(jù)用戶設備顯示端的尺寸不同,自動調整亞東建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)從事“亞東網(wǎng)站設計”,“亞東網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。
Go語言提倡使用通信的方法代替共享內存,當一個資源需要在 goroutine 之間共享時,通道在 goroutine 之間架起了一個管道,并提供了確保同步交換數(shù)據(jù)的機制。聲明通道時,需要指定將要被共享的數(shù)據(jù)的類型??梢酝ㄟ^通道共享內置類型、命名類型、結構類型和引用類型的值或者指針。
這里通信的方法就是使用通道(channel),如下圖所示。
圖:goroutine 與 channel 的通信
在地鐵站、食堂、洗手間等公共場所人很多的情況下,大家養(yǎng)成了排隊的習慣,目的也是避免擁擠、插隊導致的低效的資源使用和交換過程。代碼與數(shù)據(jù)也是如此,多個 goroutine 為了爭搶數(shù)據(jù),勢必造成執(zhí)行的低效率,使用隊列的方式是最高效的,channel 就是一種隊列一樣的結構。
通道的特性
Go語言中的通道(channel)是一種特殊的類型。在任何時候,同時只能有一個 goroutine 訪問通道進行發(fā)送和獲取數(shù)據(jù)。goroutine 間通過通道就可以通信。
通道像一個傳送帶或者隊列,總是遵循先入先出(First In First Out)的規(guī)則,保證收發(fā)數(shù)據(jù)的順序。
聲明通道類型
通道本身需要一個類型進行修飾,就像切片類型需要標識元素類型。通道的元素類型就是在其內部傳輸?shù)臄?shù)據(jù)類型,聲明如下:
var 通道變量 chan 通道類型
- 通道類型:通道內的數(shù)據(jù)類型。
- 通道變量:保存通道的變量。
chan 類型的空值是 nil,聲明后需要配合 make 后才能使用。
創(chuàng)建通道
通道是引用類型,需要使用 make 進行創(chuàng)建,格式如下:
通道實例 := make(chan 數(shù)據(jù)類型)
- 數(shù)據(jù)類型:通道內傳輸?shù)脑仡愋汀?/li>
- 通道實例:通過make創(chuàng)建的通道句柄。
請看下面的例子:
ch1 := make(chan int) // 創(chuàng)建一個整型類型的通道
ch2 := make(chan interface{}) // 創(chuàng)建一個空接口類型的通道, 可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip) // 創(chuàng)建Equip指針類型的通道, 可以存放*Equip
使用通道發(fā)送數(shù)據(jù)
通道創(chuàng)建后,就可以使用通道進行發(fā)送和接收操作。
1) 通道發(fā)送數(shù)據(jù)的格式
通道的發(fā)送使用特殊的操作符<-,將數(shù)據(jù)通過通道發(fā)送的格式為:
通道變量 <- 值
- 通道變量:通過make創(chuàng)建好的通道實例。
- 值:可以是變量、常量、表達式或者函數(shù)返回值等。值的類型必須與ch通道的元素類型一致。
2) 通過通道發(fā)送數(shù)據(jù)的例子
使用 make 創(chuàng)建一個通道后,就可以使用<-向通道發(fā)送數(shù)據(jù),代碼如下:
// 創(chuàng)建一個空接口通道
ch := make(chan interface{})
// 將0放入通道中
ch <- 0
// 將hello字符串放入通道中
ch <- "hello"
3) 發(fā)送將持續(xù)阻塞直到數(shù)據(jù)被接收
把數(shù)據(jù)往通道中發(fā)送時,如果接收方一直都沒有接收,那么發(fā)送操作將持續(xù)阻塞。Go 程序運行時能智能地發(fā)現(xiàn)一些永遠無法發(fā)送成功的語句并做出提示,代碼如下:
package main
func main() {
// 創(chuàng)建一個整型通道
ch := make(chan int)
// 嘗試將0通過通道發(fā)送
ch <- 0
}運行代碼,報錯:
fatal error: all goroutines are asleep - deadlock!
報錯的意思是:運行時發(fā)現(xiàn)所有的 goroutine(包括main)都處于等待 goroutine。也就是說所有 goroutine 中的 channel 并沒有形成發(fā)送和接收對應的代碼。
使用通道接收數(shù)據(jù)
通道接收同樣使用<-操作符,通道接收有如下特性:
① 通道的收發(fā)操作在不同的兩個 goroutine 間進行。
由于通道的數(shù)據(jù)在沒有接收方處理時,數(shù)據(jù)發(fā)送方會持續(xù)阻塞,因此通道的接收必定在另外一個 goroutine 中進行。
② 接收將持續(xù)阻塞直到發(fā)送方發(fā)送數(shù)據(jù)。
如果接收方接收時,通道中沒有發(fā)送方發(fā)送數(shù)據(jù),接收方也會發(fā)生阻塞,直到發(fā)送方發(fā)送數(shù)據(jù)為止。
③ 每次接收一個元素。
通道一次只能接收一個數(shù)據(jù)元素。
通道的數(shù)據(jù)接收一共有以下 4 種寫法。
1) 阻塞接收數(shù)據(jù)
阻塞模式接收數(shù)據(jù)時,將接收變量作為<-操作符的左值,格式如下:
data := <-ch
執(zhí)行該語句時將會阻塞,直到接收到數(shù)據(jù)并賦值給 data 變量。
2) 非阻塞接收數(shù)據(jù)
使用非阻塞方式從通道接收數(shù)據(jù)時,語句不會發(fā)生阻塞,格式如下:
data, ok := <-ch
- data:表示接收到的數(shù)據(jù)。未接收到數(shù)據(jù)時,data 為通道類型的零值。
- ok:表示是否接收到數(shù)據(jù)。
非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要實現(xiàn)接收超時檢測,可以配合 select 和計時器 channel 進行,可以參見后面的內容。
3) 接收任意數(shù)據(jù),忽略接收的數(shù)據(jù)
阻塞接收數(shù)據(jù)后,忽略從通道返回的數(shù)據(jù),格式如下:
<-ch
執(zhí)行該語句時將會發(fā)生阻塞,直到接收到數(shù)據(jù),但接收到的數(shù)據(jù)會被忽略。這個方式實際上只是通過通道在 goroutine 間阻塞收發(fā)實現(xiàn)并發(fā)同步。
使用通道做并發(fā)同步的寫法,可以參考下面的例子:
package main
import (
"fmt"
)
func main() {
// 構建一個通道
ch := make(chan int)
// 開啟一個并發(fā)匿名函數(shù)
go func() {
fmt.Println("start goroutine")
// 通過通道通知main的goroutine
ch <- 0
fmt.Println("exit goroutine")
}()
fmt.Println("wait goroutine")
// 等待匿名goroutine
<-ch
fmt.Println("all done")
}執(zhí)行代碼,輸出如下:
wait goroutine
start goroutine
exit goroutine
all done
代碼說明如下:
- 第 10 行,構建一個同步用的通道。
- 第 13 行,開啟一個匿名函數(shù)的并發(fā)。
- 第 18 行,匿名 goroutine 即將結束時,通過通道通知 main 的 goroutine,這一句會一直阻塞直到 main 的 goroutine 接收為止。
- 第 27 行,開啟 goroutine 后,馬上通過通道等待匿名 goroutine 結束。
4) 循環(huán)接收
通道的數(shù)據(jù)接收可以借用 for range 語句進行多個元素的接收操作,格式如下:
for data := range ch {
}通道 ch 是可以進行遍歷的,遍歷的結果就是接收到的數(shù)據(jù)。數(shù)據(jù)類型就是通道的數(shù)據(jù)類型。通過 for 遍歷獲得的變量只有一個,即上面例子中的 data。
遍歷通道數(shù)據(jù)的例子請參考下面的代碼。
使用 for 從通道中接收數(shù)據(jù):
package main
import (
"fmt"
"time"
)
func main() {
// 構建一個通道
ch := make(chan int)
// 開啟一個并發(fā)匿名函數(shù)
go func() {
// 從3循環(huán)到0
for i := 3; i >= 0; i-- {
// 發(fā)送3到0之間的數(shù)值
ch <- i
// 每次發(fā)送完時等待
time.Sleep(time.Second)
}
}()
// 遍歷接收通道數(shù)據(jù)
for data := range ch {
// 打印通道數(shù)據(jù)
fmt.Println(data)
// 當遇到數(shù)據(jù)0時, 退出接收循環(huán)
if data == 0 {
break
}
}
}執(zhí)行代碼,輸出如下:
3
2
1
0
代碼說明如下:
- 第 12 行,通過 make 生成一個整型元素的通道。
- 第 15 行,將匿名函數(shù)并發(fā)執(zhí)行。
- 第 18 行,用循環(huán)生成 3 到 0 之間的數(shù)值。
- 第 21 行,將 3 到 0 之間的數(shù)值依次發(fā)送到通道 ch 中。
- 第 24 行,每次發(fā)送后暫停 1 秒。
- 第 30 行,使用 for 從通道中接收數(shù)據(jù)。
- 第 33 行,將接收到的數(shù)據(jù)打印出來。
- 第 36 行,當接收到數(shù)值 0 時,停止接收。如果繼續(xù)發(fā)送,由于接收 goroutine 已經(jīng)退出,沒有 goroutine 發(fā)送到通道,因此運行時將會觸發(fā)宕機報錯。
文章名稱:創(chuàng)新互聯(lián)GO教程:Go語言通道(chan)——goroutine之間通信的管道
本文網(wǎng)址:http://fisionsoft.com.cn/article/cooigdd.html


咨詢
建站咨詢
