有時你可能會發現自己考慮將一個或多個done通道合并到一個done通道中,該通道在任何組件通道關閉時關閉。編寫一個執行這種耦合度較高的select語句是可行的,盡管很冗長;但是有時你無法知道運行狀態下done通道的數量。在這種情況下,或者你如果喜歡單線操作,你可以使用or通道模式將這些通道組合在一起(如果你對done通道看著有點懵,可以先看看上一節)。
這種模式使用遞歸和goroutine創建一個復合done通道。 我們來看一下:
```
var or func(channels ...<-chan interface{}) <-chan interface{}
or = func(channels ...<-chan interface{}) <-chan interface{} { //1
switch len(channels) {
case 0: //2
return nil
case 1: //3
return channels[0]
}
orDone := make(chan interface{})
go func() { //4
defer close(orDone)
switch len(channels) {
case 2: //5
select {
case <-channels[0]:
case <-channels[1]:
}
default: //6
select {
case <-channels[0]:
case <-channels[1]:
case <-channels[2]:
case <-or(append(channels[3:], orDone)...): //6
}
}
}()
return orDone
}
```
1. 這里我們建立了名為or的函數,接收數量可變的通道并返回單個通道。
2. 由于這是個遞歸函數,我們必須設置終止條件。第一個條件是,如果傳入的切片是空的,我們簡單的返回一個nil通道。這與不傳遞通道的想法一致:我們不希望復合通道做任何事。
3. 第二個遞歸終止條件是,如果切片只含有一個元素,我們就返回給元素。
4. 這是該函數最重要的部分,也是遞歸產生的地方。我們建立一個goroutine,以便可以不受阻塞地等待我們通道上的消息。
5. 由于我們這里是遞歸的,每次遞歸調用將至少有兩個通道。作為保持goroutine數量受到限制的優化方法,們在這里為僅使用兩個通道的時設置了一個特殊情況。
6. 在這里,我們遞歸地在第三個索引之后,從我們切片中的所有通道中創建一個or通道,然后從中選擇。遞歸操作會逐層累計直到取到第一個通道元素。我們在其中傳遞了orDone通道,這樣當該樹狀結構頂層的goroutines退出時,結構底層的goroutines也會退出。
這是一種奇妙的做法,你可以將任意數量的通道組合到單個通道中,只要任何作為組件的通道關閉或被寫入,整個通道就會關閉。讓我們來看看該如何進行實際操作。下面這個例子將經過一段時間后關閉通道,然后使用or函數將這些通道合并到一個關閉的通道中:
```
sig := func(after time.Duration) <-chan interface{} { //1
c := make(chan interface{})
go func() {
defer close(c)
time.Sleep(after)
}()
return c
}
start := time.Now() //2
<-or(sig(2*time.Hour), sig(5*time.Minute), sig(1*time.Second), sig(1*time.Hour), sig(1*time.Minute))
fmt.Printf("done after %v", time.Since(start)) //3
```
1. 此功能只是創建了一個通道,當后續時間中指定的時間結束時將關閉該通道。
2. 在這里,我們設置追蹤自or函數的通道開始阻塞的起始時間。
3. 在這里我們打印阻塞發生的時間。
這會輸出:
```
done after 1.000216772s
```
請注意,盡管在我們的調用中放置了多個通道需要多個時間才能關閉,但我們在一秒鐘后關閉的通道會導致由該d調用創建的整個通道關閉。 這是因為它位于樹或函數構建的樹中,它將始終第一個關閉,因此依賴于其關閉的通道也將關閉。
我們以額外創建 f(x)=x/2 個goroutine以"簡潔的"實現該目的,其中x是goroutine的數量。請記住Go的一個優點是能夠快速創建,調度和運行goroutines,并且 該語言積極鼓勵使用goroutines來正確建模問題。無需在前期太擔心在這里創建的分支太多。如果在編譯時你不知道自己正在使用多少個done通道,那么恐怕就沒有其他更好的方法來合并done通道了。
這種模式適用于系統中模塊的交叉點。在這些交叉點,有多種條件通過你的調用堆棧取消goroutines樹。 使用or函數,你可以簡單地將它們組合在一起并將其傳遞給堆棧。 我們將在“context包”中看到另一種更具描述性的做法。
我們也將看到這種模式的變體在第五章“重復請求”中形成更復雜的模式。
* * * * *
學識淺薄,錯誤在所難免。我是長風,歡迎來Golang中國的群(211938256)就本書提出修改意見。
- 前序
- 誰適合讀這本書
- 章節導讀
- 在線資源
- 第一章 并發編程介紹
- 摩爾定律,可伸縮網絡和我們所處的困境
- 為什么并發編程如此困難
- 數據競爭
- 原子性
- 內存訪問同步
- 死鎖,活鎖和鎖的饑餓問題
- 死鎖
- 活鎖
- 饑餓
- 并發安全性
- 優雅的面對復雜性
- 第二章 代碼建模:序列化交互處理
- 并發與并行
- 什么是CSP
- CSP在Go中的衍生物
- Go的并發哲學
- 第三章 Go的并發構建模塊
- Goroutines
- sync包
- WaitGroup
- Mutex和RWMutex
- Cond
- Once
- Pool
- Channels
- select語句
- GOMAXPROCS
- 結論
- 第四章 Go的并發編程范式
- 訪問范圍約束
- fo-select循環
- 防止Goroutine泄漏
- or-channel
- 錯誤處理
- 管道
- 構建管道的最佳實踐
- 便利的生成器
- 扇入扇出
- or-done-channel
- tee-channel
- bridge-channel
- 隊列
- context包
- 小結
- 第五章 可伸縮并發設計
- 錯誤傳遞
- 超時和取消
- 心跳
- 請求并發復制處理
- 速率限制
- Goroutines異常行為修復
- 本章小結
- 第六章 Goroutines和Go運行時
- 任務調度