如何优雅的关闭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的性质。
go
func IsClosed(ch <-chan T) bool {
select {
case <-ch:
return true
default:
}
return false
}
func main() {
c := make(chan T)
fmt.Println(IsClosed(c)) // false
close(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 监听到关闭信号后,停止接收数据。
go
func main() {
rand.Seed(time.Now().UnixNano())
const Max = 100000
const NumSenders = 1000
dataCh := make(chan int, 100)
stopCh := make(chan struct{})
// senders
for i := 0; i < NumSenders; i++ {
go func() {
for {
select {
case <- stopCh:
return
case dataCh <- rand.Intn(Max):
}
}
}()
}
// the receiver
go 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),可以避免阻塞问题。
此部分代码见参考链接的原文即可