目錄
- 無緩沖channel等價(jià)于緩沖大小為0的channel,而不是1
- 發(fā)送者和接收者哪些情況會(huì)阻塞
- close哪些情況會(huì)導(dǎo)致panic
- 如何優(yōu)雅的關(guān)閉channel
- 當(dāng)一個(gè)select中有多個(gè)channel滿足可讀時(shí),誰被激活
- select with default
- 讀取時(shí)獲取第二個(gè)返回值,以此判斷該channel是否被關(guān)閉
- close前寫入的數(shù)據(jù),接收者依然可以按順序讀取到
- 一個(gè)channel有多個(gè)接收者時(shí),close channel會(huì)喚醒所有接收者
- 配合timer實(shí)現(xiàn)channel讀取的超時(shí)機(jī)制
- 當(dāng)channel只用做同步通知,不關(guān)心channel中傳輸?shù)闹禃r(shí),可使用
chan struct{}類型 - 單向channel類型的作用
- 可以make單向channel,但是這樣做沒有意義
- channel配合
for range的簡(jiǎn)化寫法
1. 無緩沖channel等價(jià)于緩沖大小為0的channel,而不是1
var ch chan int // ch == nil
// 創(chuàng)建無緩沖channel
ch := make(chan int) // ch != nil
// 等價(jià)于
// ch := make(chan int, 0)
// 不等價(jià)于
// ch := make(chan int, 1)
close(ch) // close執(zhí)行后, ch != nil
2. 發(fā)送者和接收者哪些情況會(huì)阻塞
- 往值為nil的channel發(fā)送數(shù)據(jù): 永久阻塞
- 從值為nil的channel讀取數(shù)據(jù): 永久阻塞
- 無緩沖模式的發(fā)送者: 阻塞直到數(shù)據(jù)被接收者接收
- 無緩沖模式的接收者: 無數(shù)據(jù)可讀時(shí),阻塞
- 有緩沖模式的發(fā)送者: 當(dāng)緩沖滿時(shí),阻塞
- 有緩沖模式的接收者: 無數(shù)據(jù)可讀時(shí),阻塞
3. close哪些情況會(huì)導(dǎo)致panic
- close值為nil的channel
- close已經(jīng)被close的channel
- 向已經(jīng)被close的channel發(fā)送數(shù)據(jù)
4. 如何優(yōu)雅的關(guān)閉channel
需要特別注意:
- 接收者關(guān)閉channel要小心,因?yàn)殛P(guān)閉后發(fā)送者繼續(xù)發(fā)送會(huì)panic
- 當(dāng)有多個(gè)發(fā)送者時(shí),在一個(gè)發(fā)送者協(xié)程中關(guān)閉channel要小心,因?yàn)殛P(guān)閉后其他發(fā)送者繼續(xù)發(fā)送會(huì)panic
復(fù)雜情況下的參考思路:
- channel的關(guān)閉并非必須的,只要channel對(duì)象不再被持有,垃圾回收器會(huì)清理它
- 可使用原子變量等同步原語(yǔ)保證close有且只有發(fā)生一次
- 除了傳輸數(shù)據(jù)的channel,可以再增加channel配合select使用,用于取消生產(chǎn)、消費(fèi)
- 接收端也可以通過其他channel發(fā)出消息,反向通知發(fā)送端
5. 當(dāng)一個(gè)select中有多個(gè)channel滿足可讀時(shí),誰被激活
Go隨機(jī)選取一個(gè)滿足條件的case分支執(zhí)行,而不是按代碼順序選取。
// 如下代碼段,可能輸出ch1,也可能輸出ch2
ch1 := make(chan int, 8)
ch2 := make(chan int, 8)
ch1 <- 1
ch2 <- 1
select {
case <- ch1:
fmt.Println("ch1")
case <- ch2:
fmt.Println("ch2")
}
6. select with default
當(dāng)select中的條件都不滿足時(shí),會(huì)立即執(zhí)行default分支
// 如下代碼段,會(huì)立即打印default
ch1 := make(chan int, 1)
ch2 := make(chan int)
select {
case <- ch1:
fmt.Println("ch1")
case <- ch2:
fmt.Println("ch2")
default:
fmt.Println("default")
}
7. 讀取時(shí)獲取第二個(gè)返回值,以此判斷該channel是否被關(guān)閉
v, ok := <- ch
// 當(dāng)channel被關(guān)閉后,v為channel類型的零值,ok為false
8. close前寫入的數(shù)據(jù),接收者依然可以按順序讀取到
ch := make(chan int, 8)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for {
v, ok := <- ch
fmt.Println(v, ok)
if !ok {
break
}
}
// 以上代碼段,將打印出如下結(jié)果:
// 1 true
// 2 true
// 3 true
// 0 false
9. 一個(gè)channel有多個(gè)接收者時(shí),close channel會(huì)喚醒所有接收者
10. 配合timer實(shí)現(xiàn)channel讀取的超時(shí)機(jī)制
但在高性能場(chǎng)景需要注意,參見: golang源碼閱讀之定時(shí)器以及避坑指南
11. 當(dāng)channel只用做同步通知,不關(guān)心channel中傳輸?shù)闹禃r(shí),可使用 chan struct{} 類型
好處是語(yǔ)意上更正確,代碼可讀性更高。
// 初始化
ch := make(chan struct{}, 1)
// 寫入
ch <- chan struct{}{}
12. 單向channel類型的作用
// 比如Go系統(tǒng)庫(kù)中Timer的實(shí)現(xiàn),就使用了只讀類型的channel,
// 目的是限制該channel在上層Timer對(duì)象只能讀,不能寫。這種限制是編譯期的
// 提供給用戶的Timer對(duì)象
type Timer struct {
C <-chan Time // 只讀類型
r runtimeTimer
}
// 實(shí)際make的是chan Time類型
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1)
t := &Timer{
C: c, // chan Time轉(zhuǎn)換成只讀類型,后續(xù)通過Timer對(duì)象訪問C數(shù)據(jù)成員的操作只能讀,不能寫
r: runtimeTimer{
when: when(d),
f: sendTime,
arg: c, // 將chan Time類型傳遞給底層runtimeTimer中使用,底層可以寫
},
}
startTimer(&t.r)
return t
}
// 用戶調(diào)用After時(shí),返回只讀類型的channel
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
13. 可以make單向channel,但是這樣做沒有意義
// 以下初始化了一個(gè)只寫的channel是合法的,但是只能寫,不能讀,應(yīng)該沒有這種使用場(chǎng)景
ch := make(chan<- int, 4)
14. channel配合for range的簡(jiǎn)化寫法
ch := make(chan int, 4)
ch <- 1
ch <- 2
close(ch)
for v := range ch {
fmt.Println(v)
}
fmt.Println("< for")
// 以上代碼段將打印如下結(jié)果
// 1
// 2
// < for
參考鏈接
- A Tour of Go (https://tour.golang.org/concurrency/2)
- Effective Go (https://golang.org/doc/effective_go.html#channels)
- Golang Concurrency Tricks (http://udhos.github.io/golang-concurrency-tricks/)
- How to Gracefully Close Channels (https://go101.org/article/channel-closing.html)
- 深入理解channel:設(shè)計(jì)+源碼 (https://mp.weixin.qq.com/s/NXpTwMQtHwHMSJq7NDjn-A)
備忘錄類型文章,后續(xù)的修改會(huì)第一時(shí)間在我的個(gè)人站點(diǎn)更新,地址: https://pengrl.com/p/23102