二分递归版orDone的问题

Go语言精选

共 3205字,需浏览 7分钟

 · 2021-09-07

今天在修正昨天的文章《orDone 的两种实现》中的压测代码时,无意发现其中的二分递归版的代码是有问题的。

主要是goroutine泄露的问题,下边简单说明下:

(参考自文章 记一次学习 orDone 模式爬坑经历[1] 

goroutine 泄露

orDone := make(chan interface{})
go func() {
    defer close(orDone)
    switch len(channels) {
    case 2// 2个也是一种特殊情况
        ...
    default//超过两个,二分法递归处理
        m := len(channels) / 2
        select {
        case <-or(channels[:m]...):
        case <-or(channels[m:]...):
        }
    }
}()

原代码递归时,没有将结束通道orDone合并,在orDone关闭后,没法通知递归中的协程退出,有goroutine泄露的可能。可修改为

select {
   case <-OrWithIssue(append(channels[:m:m], orDone)...):
   case <-OrWithIssue(append(channels[m:], orDone)...):
}

无限递归

以上代码时,还有个问题是在参数为三个chan时会无限递归,(文末参考文章里有通过打印协程数来测试这个问题的代码,感兴趣可以去看下)

递归树如下:

// 3个时有无限递归的问题:
    f(3)
f(2)  f(3)
    f(2)  f(3)
       f(2)  f(3)
                ...

所以需要对 3 这种case区分处理

最终代码如下:

func OrRecur(channels ...<-chan interface{}) <-chan interface{} {
 // 特殊情况,只有0个或者1个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个,二分法递归处理
   m := len(channels) / 2
   select {
   case <-OrRecur(append(channels[:m:m], orDone)...):
   case <-OrRecur(append(channels[m:], orDone)...):
   }
  }
 }()

 return orDone
}

最后,再补充下修正后压测结果,基本上,二分递归比反射性能更好些

感兴趣可以自己跑下压测代码[2]

虽然是常见的 orDone 模式,但还是有不少可以探究的地方,想要用好 chan 还是需要足够仔细啊。

参考资料

[1]

记一次学习 orDone 模式爬坑经历: https://tjjsjwhj.me/2021/04/25/go-or-done/

[2]

压测代码: https://github.com/NewbMiao/Dig101-Go/tree/master/concurrency/channel/schedule/orDone



推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。

浏览 9
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报