如何优雅的关闭Channel
这部分主要参考自:https://qcrao91.gitbook.io/go/channel/ru-he-you-ya-di-guan-bi-channel
直接关闭存在的问题
主要就是上述“向已关闭的Channel收发,会如何?”中所提到的情况:
1、向已关闭的channel中发送数据,会panic
2、重复关闭已经关闭的channel,会panic。
3、从已关闭的channel接收数据,收到的是0。
一个比较粗糙的实现
利用从读channel的,会返回bool的性质。
func IsClosed(ch <-chan T) bool {select {case <-ch:return truedefault:}return false
}func main() {c := make(chan T)fmt.Println(IsClosed(c)) // falseclose(c)fmt.Println(IsClosed(c)) // true
}
但这样比较粗糙:
一来,对channel的状态进行了修改。
二来,检测的瞬间和关闭瞬间有间隔。
三来,多个同时调用的话,也可能重复关闭。
合理的方案
don't close a channel from the receiver side and don't close a channel if the channel has multiple concurrent senders.
即:不要从一个 receiver 侧关闭 channel,也不要在有多个 sender 时,关闭 channel。
因此考虑从发送端进行关闭。
单一sender的情况
即:
一个 sender,一个 receiver
一个 sender, M 个 receiver
对于这种情况,直接sender方,发完之后关了即可。
多个sender的情况
即:
N 个 sender,一个 reciver
N 个 sender, M 个 receiver
针对一个reciver的情况:
增加一个传递关闭信号的 channel,receiver 通过信号 channel 下达关闭数据 channel 指令。senders 监听到关闭信号后,停止接收数据。
func main() {rand.Seed(time.Now().UnixNano())const Max = 100000const NumSenders = 1000dataCh := make(chan int, 100)stopCh := make(chan struct{})// sendersfor i := 0; i < NumSenders; i++ {go func() {for {select {case <- stopCh:returncase dataCh <- rand.Intn(Max):}}}()}// the receivergo func() {for value := range dataCh {if value == Max-1 {fmt.Println("send stop signal to senders.")close(stopCh)return}fmt.Println(value)}}()select {case <- time.After(time.Hour):}
}
注意:这个代码中,其实并没有关闭channel。这个优雅的处理就是指的:不用他了,将他交给Golang的GC机制去处理。
针对多个reciver的情况:
如果依旧采取上述方案,则可能遇到的情况是:下达了多个关闭命令,依旧造成“向已关闭的channel进行关闭”。因此使用一个“中间人”channel,reciver都向他发送stop,收到第一个后 就向sender发stop。reciver默认长度设置为:Num(senders) + Num(receivers),可以避免阻塞问题。
此部分代码见参考链接的原文即可