如何安全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
------------------------------------------------------------------------------------------------
一定要专业!本博客定位于 ,C语言,C++语言,Java语言,Android开发和少量的Web开发,之前是做Web开发的,其实就是ASP维护,发现EasyASP这个好框架,对前端后端数据库 都很感觉亲切啊。. linux,总之后台开发多一点。以后也愿意学习 cocos2d-x 游戏客户端的开发。