二分遞歸版orDone的問題
今天在修正昨天的文章《orDone 的兩種實(shí)現(xiàn)》中的壓測(cè)代碼時(shí),無意發(fā)現(xiàn)其中的二分遞歸版的代碼是有問題的。
主要是goroutine泄露的問題,下邊簡(jiǎn)單說明下:
(參考自文章 記一次學(xué)習(xí) orDone 模式爬坑經(jīng)歷[1] )
goroutine 泄露
orDone := make(chan interface{})
go func() {
defer close(orDone)
switch len(channels) {
case 2: // 2個(gè)也是一種特殊情況
...
default: //超過兩個(gè),二分法遞歸處理
m := len(channels) / 2
select {
case <-or(channels[:m]...):
case <-or(channels[m:]...):
}
}
}()
原代碼遞歸時(shí),沒有將結(jié)束通道orDone合并,在orDone關(guān)閉后,沒法通知遞歸中的協(xié)程退出,有goroutine泄露的可能。可修改為
select {
case <-OrWithIssue(append(channels[:m:m], orDone)...):
case <-OrWithIssue(append(channels[m:], orDone)...):
}
無限遞歸
以上代碼時(shí),還有個(gè)問題是在參數(shù)為三個(gè)chan時(shí)會(huì)無限遞歸,(文末參考文章里有通過打印協(xié)程數(shù)來測(cè)試這個(gè)問題的代碼,感興趣可以去看下)
遞歸樹如下:
// 3個(gè)時(shí)有無限遞歸的問題:
f(3)
f(2) f(3)
f(2) f(3)
f(2) f(3)
...
所以需要對(duì) 3 這種case區(qū)分處理
最終代碼如下:
func OrRecur(channels ...<-chan interface{}) <-chan interface{} {
// 特殊情況,只有0個(gè)或者1個(gè)chan
switch len(channels) {
case 0:
return nil
case 1:
return channels[0]
}
orDone := make(chan interface{})
go func() {
defer close(orDone)
switch len(channels) {
case 2: // 特殊情況
select {
case <-channels[0]:
case <-channels[1]:
}
case 3: // 特殊情況
select {
case <-channels[0]:
case <-channels[1]:
case <-channels[2]:
}
default: // 超過3個(gè),二分法遞歸處理
m := len(channels) / 2
select {
case <-OrRecur(append(channels[:m:m], orDone)...):
case <-OrRecur(append(channels[m:], orDone)...):
}
}
}()
return orDone
}
最后,再補(bǔ)充下修正后壓測(cè)結(jié)果,基本上,二分遞歸比反射性能更好些


感興趣可以自己跑下壓測(cè)代碼[2]
雖然是常見的 orDone 模式,但還是有不少可以探究的地方,想要用好 chan 還是需要足夠仔細(xì)啊。
參考資料
記一次學(xué)習(xí) orDone 模式爬坑經(jīng)歷: https://tjjsjwhj.me/2021/04/25/go-or-done/
[2]壓測(cè)代碼: https://github.com/NewbMiao/Dig101-Go/tree/master/concurrency/channel/schedule/orDone
推薦閱讀
評(píng)論
圖片
表情
