Go - channel
channel
一個 channels 是一個通訊機制,是goroutine之間的通訊機制
Go語言提倡使用通訊的方法代替共享記憶體,當一個資源需要在 goroutine 之間共享時,通道在 goroutine 之間架起了一個管道,並提供了確保同步交換資料的機制。宣告通道時,需要指定將要被共享的資料的型別。可以通過通道共享內建型別、命名型別、結構型別和引用型別的值或者指標。
在任何時候,同時只能有一個 goroutine 訪問通道進行傳送和獲取資料,總是遵循先入先出(First In First Out)的規則,保證收發資料的順序
宣告通道型別
var c chan int
chan 型別的空值是 nil,聲明後需要配合 make 後才能使用
建立通道
c1 := make(chan int) //整型型別通道
c2 := make(chan interface{}) //可以放任意型別
c3 := make(chan * Struct) //指標通道
通道發資料
- 把資料往通道中傳送時,如果接收方一直都沒有接收,那麼傳送操作將持續阻塞。Go 程式執行時能智慧地發現一些永遠無法傳送成功的語句並做出提示
c1 <- 1
c2 <- "xx"
通道接收資料
- 由於通道的資料在沒有接收方處理時,資料傳送方會持續阻塞,因此通道的接收必定在另外一個 goroutine 中進行
- 如果接收方接收時,通道中沒有傳送方傳送資料,接收方也會發生阻塞,直到傳送方傳送資料為止
- 通道一次只能接收一個數據元素
//阻塞接收資料
a:= <- c1
//非阻塞接收資料
data,ok:=<- c2
//忽略接收的資料,執行該語句時將會發生阻塞,直到接收到資料,但接收到的資料會被忽略
<- c2
//迴圈接收
for data := range ch {
}
非阻塞的通道接收方法可能造成高的 CPU 佔用,因此使用非常少。如果需要實現接收超時檢測,可以配合 select 和計時器 channel 進行
示例:
var (
ch = make(chan int)
)
func main() {
fmt.Println("開始生產資料")
go task()
for i := 1; i < 10; i++ {
ch <- i
}
fmt.Println("我沒有資料了")
ch <- 0
<-ch
fmt.Println("生產結束")
}
func task() {
for {
data := <-ch
fmt.Printf("消費了資料%d\n", data)
if data == 0 {
fmt.Println("我收到結束資訊了")
break
}
}
ch <- 0
}
單向通道的宣告
ch := make(chan int)
// 宣告一個只能寫入資料的通道型別, 並賦值為ch
var c1 chan <- int = ch
//宣告一個只能讀取資料的通道型別, 並賦值為ch
var c2 <- chang int = ch
關閉 channel
close(ch)
//ok 可以驗證通道已經關閉了
x, ok := <-ch
示例:
var (
ch = make(chan int)
)
func main() {
go product()
for {
data, ok := <-ch
if !ok {
break
}
fmt.Println(data)
}
fmt.Println("程式結束")
}
func product() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
fmt.Println("生產結束")
}
無緩衝通道
Go語言中無緩衝的通道(unbuffered channel)是指在接收前沒有能力儲存任何值的通道。這種型別的通道要求傳送 goroutine 和接收 goroutine 同時準備好,才能完成傳送和接收操作
緩衝通道
Go語言中有緩衝的通道(buffered channel)是一種在被接收前能儲存一個或者多個值的通道。這種型別的通道並不強制要求 goroutine 之間必須同時完成傳送和接收。通道會阻塞傳送和接收動作的條件也會不同。只有在通道中沒有要接收的值時,接收動作才會阻塞。只有在通道沒有可用緩衝區容納被髮送的值時,傳送動作才會阻塞。
// 建立一個3個元素緩衝大小的整型通道
ch := make(chan int, 3)
// 檢視當前通道的大小
fmt.Println(len(ch))
通道超時
可以使用 select 來設定超時
超時機制本身雖然也會帶來一些問題,比如在執行比較快的機器或者高速的網路上執行正常的程式,到了慢速的機器或者網路上執行就會出問題,從而出現結果不一致的現象,但從根本上來說,解決死鎖問題的價值要遠大於所帶來的問題。
select 有比較多的限制,其中最大的一條限制就是每個 case 語句裡必須是一個 IO 操作
-
如果其中的任意一語句可以繼續執行(即沒有被阻塞),那麼就從那些可以執行的語句中任意選擇一條來使用。
-
如果沒有任意一條語句可以執行(即所有的通道都被阻塞),那麼有如下兩種可能的情況:
- 如果給出了 default 語句,那麼就會執行 default 語句,同時程式的執行會從 select 語句後的語句中恢復;
- 如果沒有 default 語句,那麼 select 語句將被阻塞,直到至少有一個通訊可以進行下去。
for {
select {
case num := <-ch:
fmt.Println("num = ", num)
case <-time.After(3 * time.Second):
fmt.Println("超時")
quit <- true
}
}