页首Html代码

返回顶部

如何安全close go 的channel

golang 在多协程下,  不清楚谁是 sender 谁是 recver 的时候, close(chan) 和 chan<-data 两个都很容易报错

 原则:

//1  close 永远和data分离, 不要close 还会发送或接受的 channel
//2  close 永远要做好 'close of closed channel' 错误的发生
//3  send (也就是 <- 操作) 永远不要让他出现 'send to a nil channel' 其实也就是第一点

 

那么close 到底怎么才能更安全,更正确的,解决呢? 

单协程 直接close ,太low,我们不考虑,只考虑最复杂环境下的情况.

 

 第一种方法 暴力关闭channel ,只需要增加 recover() 就可以了

 

第二种方法  通过对变量加锁,或者直接使用sync.Once 来控制 close 只有一次

 

 第三种方法 创建两个chan,一个永远不close,只进行 无限 发送,  单个接收; 一旦接收了 ,就关闭另外一个; 另外一个 返还给外部, 只判断 close的情况,不进行发送和直接close

 

第四种方法 比较简单,总是 使用select就可以了,

done := make(chan struct{})

// 复杂环境下 并发 进行关闭
select{
      case <-done:
      default:
                close(done)
}

 

//复杂环境 并发 检测是否关闭


select{
      case <-done
             //关闭处理
             return 
       default:
            //没有关闭的处理
}

 经过高并发测试,发现 上面的select 关闭方法, 还是不安全, select不是原子性, 只要异步 数量达到值,就还是会有出现panic的情况;

测试用例:

 运行  Test_SafeCloseChan2   几乎是必现的,只不过是  成功 END 出现几次罢了.

func Test_SafeCloseChan2(t *testing.T) {
    for i := 0; i < 1000; i++ {
        Test_SafeCloseChan(t)
    }
}
func Test_SafeCloseChan(t *testing.T) {
    done := make(chan bool)
    begin := time.Now().Add(300 * time.Millisecond)
    wg := sync.WaitGroup{}
    for i := 0; i < 1000000; i++ {
        wg.Add(1)
        go func(i int) {
            defer func() {
                wg.Done()
            }()
            //等待开始,一起开始,避免不同时
            for {
                if time.Now().After(begin) {
                    break
                }
                time.Sleep(1 * time.Microsecond)
            }

            select {
            case <-done:
                //case <-time.After(500 * time.Millisecond):
                //    select {
                //    case <-done:
                //    default:
                //        close(done)
                //        t.Log("Success close(done)")
                //    }
                //case <-time.After(500 * time.Millisecond):
                //    t.Log("Success close(done)")
                //    close(done)
            default:
                close(done)
                t.Log("Success close(done)")
            }
        }(i)
    }
    wg.Wait()
    t.Log("=============END=========")
}

所以, 还是加 panic 吧

 

https://zhuanlan.zhihu.com/p/32529039

posted @ 2020-12-17 16:57  ayanmw  阅读(518)  评论(0编辑  收藏  举报

页脚Html代码