1 概述
通道是Golang提供的一種基本類型,它可以實現在協程之間的單向通信和雙向通信、發送和接收數據、以及協程同步。
channel的本質是一個隊列,遵循先進先出原則。channel是線程安全的,在任何給定時間,一個數據被設計爲只有一個協程可以對其訪問,不會發生數據競爭,多goroutine訪問時,也不需要加鎖,這些是有編譯器在底層維護的。
2 分類
在Go中,通道有兩種主要類型:
非緩衝通道:發送操作會一直阻塞,直到有一個goroutine接收到了這個消息;
帶緩衝通道:發送操作不會被阻塞,直到通道內的消息數量超過了緩衝區的大小;
3 聲明與初始化
3.1 聲明
var chanName chan toTransDatatype
var funcChan chan func(int) error
var chanChan chan chan int
通道的數據類型爲 chan,後面的 toTransDataType 表示 想要傳遞的數據類型,如 int、float、string、interface{}、結構體、指針、func()等,甚至 chan本身。
var m map[string] chan bool
上面的代碼聲明瞭一個 m 的 map,它的key爲string類型,它的value 爲 chan bool 類型
3.2 初始化
channelWithoutBuf := make(chan int) // 無緩衝通道
channelWithBuf := make(chan int, 3) // 有緩衝通道,緩衝區size爲3,提前分配了內存
3.3 示例
package main
import "fmt"
func main() {
var a chan int
fmt.Println(fmt.Sprintf("a :%+v, type: %T", a, a))
b := make(chan int)
fmt.Println(fmt.Sprintf("b :%+v, type: %T", b, b))
c := make(chan int, 3)
fmt.Println(fmt.Sprintf("c :%+v, type: %T", c, c))
a = b
fmt.Println(fmt.Sprintf("new a :%+v, type: %T", a, a))
b = c
fmt.Println(fmt.Sprintf("new b :%+v, type: %T", b, b))
return
}
輸出
a :<nil>, type: chan int
b :0x14000016540, type: chan int
c :0x14000100000, type: chan int
new a :0x14000016540, type: chan int
new b :0x14000100000, type: chan int
可以看到,通過 var 聲明的通道,值爲nil,相當於是一個空管道。
把 b 賦值給 a,此時 a 也就成了 b;同時,把 b 賦值給 c,b也會變成 c,說明初始化的不同類型通道之間是可以互相轉化的。
4 操作
通道的操作符爲 <-,語法是:
- chanName<- value:把value變量的值寫入chanName通道;
- value := <-chanName:從chanName中取出數據,賦值給value變量;
我們來試試:
package main
import "fmt"
func main() {
var chanA chan int
chanA <- 1
data := <-chanA
fmt.Println(fmt.Sprintf("data: %+v, type:%T", data, data))
//close(chanA)
//// 由管道中讀寫數據,<-操作符是與最左邊的chan優先結合的
// 向管道中寫入一個數據,在此需要注意:向管道中寫入數據通常會導致當前協程阻塞,直到有其他協程從這個管道中讀取數據
}
輸出
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send (nil chan)]:
main.main()
/Users/yiyunjie/go/src/Test/main.go:7 +0x28
Process finished with the exit code 2
可以看到,main協程非正常退出,所有的協程處於休眠狀態 - 死鎖!所謂死鎖,就是雙方都在等待對方釋放鎖。
在當前場景,就是兩個協程一直在等待對方釋放資源。而在這個例子中,只有一個main函數所在的協程,並沒有新的協程參與,爲什麼會死鎖呢?
原因在於 往一個無緩衝通道里寫入數據時,發送操作會一直阻塞、等待返回,直到有一個協程接收到了這個通道里的數據,阻塞狀態纔會解除。而上面的例子,是直接在當前協程接收數據,而當前協程一直在阻塞,不能繼續往下執行,所以接收數據的代碼根本就沒執行到。這樣程序就一直在等待一個無法釋放的鎖,形成了死鎖。Go的運行時系統檢測到這種情況後,會拋出這個錯誤,並終止程序的執行。
要解決這個問題,有兩種方法:將發送操作變成非阻塞的;保持發送操作爲阻塞的,用另一個協程接收數據;
4.1 非阻塞發送
我們知道,往帶緩衝通道里寫入數據是非阻塞的,直到通道內的消息數量超過了緩衝區的大小。那我們就定義一個帶緩衝通道試試:
package main
import (
"fmt"
)
func main() {
chanA := make(chan int, 3)
chanA <- 1
data := <-chanA
fmt.Println(fmt.Sprintf("data: %+v, type: %T", data, data))
return
}
輸出
data: 1, type: int
沒有問題,可以正常接收到數據,且類型相符。
4.2 用新協程接收非緩衝通道信息
由於往非緩衝通道發送數據是阻塞的,所以不能把接收數據的新協程寫在發送操作之後,不然當前協程一直阻塞,新協程還是無法接受到數據。所以只能把接收動作放在發送之前。
package main
import (
"fmt"
"time"
)
func main() {
chanA := make(chan int)
go func() {
data := <-chanA
fmt.Println(fmt.Sprintf("data: %+v, type: %T", data, data))
}()
chanA <- 1
time.Sleep(10 * time.Millisecond)
return
}
輸出
data: 1, type: int