channel 是 golang 里相當(dāng)有趣的一個功能,在我使用 golang 編碼的經(jīng)驗里,大部分事件都會是在享受 channel 和 goroutine 配合的樂趣。所以本文主要介紹 channel 的一些有趣的用法。
這里有 Oling Cat 翻譯的Go編程語言規(guī)范里關(guān)于 channel(信道)的描述:
信道提供了一種機制,它在兩個并發(fā)執(zhí)行的函數(shù)之間進行同步,并通過傳遞(與該信道元素類型相符的)值來進行通信。
這個個描述又乏味、又枯燥。在我第一次閱讀的時候,完全不明白這到底是個什么玩意。事實上,可以認(rèn)為 channel 是一個管道或者先進先出隊列,非常簡單且輕量。channel 并不是 Golang 首創(chuàng)的。它同樣作為內(nèi)置功能出現(xiàn)在其他語言中。在大多數(shù)情況下,它是一個又大、又笨、又復(fù)雜的消息隊列系統(tǒng)的一個功能。
本文主要講實踐,原理部分會一筆帶過,關(guān)于 go 語言并發(fā)實現(xiàn)和內(nèi)存模型后續(xù)會有文章。
channel 實現(xiàn)的源碼不復(fù)雜,推薦閱讀,https://github.com/golang/go/blob/master/src/runtime/chan.go
channel 是干什么的
意義:channel 是用來通信的
實際上:(數(shù)據(jù)拷貝了一份,并通過 channel 傳遞,本質(zhì)就是個隊列)
channel 應(yīng)該用在什么地方
核心:需要通信的地方
例如以下場景:
- 通知廣播
- 交換數(shù)據(jù)
- 顯式同步
- 并發(fā)控制
- ...
記住!channel 不是用來實現(xiàn)鎖機制的,雖然有些地方可以用它來實現(xiàn)類似讀寫鎖,保護臨界區(qū)的功能,但不要這么用!
channel 用例實現(xiàn)
超時控制
// 利用 time.After 實現(xiàn)
func main() {
done := do()
select {
case -done:
// logic
case -time.After(3 * time.Second):
// timeout
}
}
func do() -chan struct{} {
done := make(chan struct{})
go func() {
// do something
// ...
done - struct{}{}
}()
return done
}
取最快的結(jié)果
比較常見的一個場景是重試,第一個請求在指定超時時間內(nèi)沒有返回結(jié)果,這時重試第二次,取兩次中最快返回的結(jié)果使用。
超時控制在上面有,下面代碼部分就簡單實現(xiàn)調(diào)用多次了。
func main() {
ret := make(chan string, 3)
for i := 0; i cap(ret); i++ {
go call(ret)
}
fmt.Println(-ret)
}
func call(ret chan- string) {
// do something
// ...
ret - "result"
}
限制最大并發(fā)數(shù)
// 最大并發(fā)數(shù)為 2
limits := make(chan struct{}, 2)
for i := 0; i 10; i++ {
go func() {
// 緩沖區(qū)滿了就會阻塞在這
limits - struct{}{}
do()
-limits
}()
}
for...range 優(yōu)先
for ... range c { do } 這種寫法相當(dāng)于 if _, ok := -c; ok { do }
func main() {
c := make(chan int, 20)
go func() {
for i := 0; i 10; i++ {
c - i
}
close(c)
}()
// 當(dāng) c 被關(guān)閉后,取完里面的元素就會跳出循環(huán)
for x := range c {
fmt.Println(x)
}
}
多個 goroutine 同步響應(yīng)
利用 close 廣播
func main() {
c := make(chan struct{})
for i := 0; i 5; i++ {
go do(c)
}
close(c)
}
func do(c -chan struct{}) {
// 會阻塞直到收到 close
-c
fmt.Println("hello")
}
非阻塞的 select
select 本身是阻塞的,當(dāng)所有分支都不滿足就會一直阻塞,如果想不阻塞,那么一個什么都不干的 default 分支是最好的選擇
select {
case -done:
return
default:
}
for{select{}} 終止
盡量不要用 break label 形式,而是把終止循環(huán)的條件放到 for 條件里來實現(xiàn)
for ok {
select {
case ch - 0:
case -done:
ok = false
}
}
channel 特性
基礎(chǔ)特性
操作 |
值為 nil 的 channel |
被關(guān)閉的 channel |
正常的 channel |
close |
panic |
panic |
成功關(guān)閉 |
c- |
永遠(yuǎn)阻塞 |
panic |
阻塞或成功發(fā)送 |
-c |
永遠(yuǎn)阻塞 |
永遠(yuǎn)不阻塞 |
阻塞或成功接收 |
happens-before 特性
- 無緩沖時,接收 happens-before 發(fā)送
- 任何情況下,發(fā)送 happens-before 接收
- close happens-before 接收
參考
https://go101.org/article/channel.html
https://golang.org/doc/effective_go.html#channels
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
您可能感興趣的文章:- golang判斷chan channel是否關(guān)閉的方法
- Golang中channel使用的一些小技巧
- Go語言的管道Channel用法實例
- Golang優(yōu)雅關(guān)閉channel的方法示例
- golang中單向channel的語法介紹
- Go語言中 Channel 詳解
- Go中Channel發(fā)送和接收操作指南