好吧昨天面试腾讯挂了,其中有个问题是 “往一个已经关闭的channel写会怎样?(会panic)那怎么保证你写入的时候channel没有被关闭呢?换句话说就是怎么判断你的channel是否已经关闭?”
go为什么没有判断channel已经close的接口?相信大家应该都遇到过 “send on closed channel” 的 panic 。这个 panic 是当你想往一个已经 close 的 channel 里面写值的时候触发。那么你当你第一次遇到这个问题是否想过 channel 是否能提供一个接口方法来判断是否已经 close 了?但是channel的源码中并没有针对是否close判断
思考方法一:通过写channel 实现ch := make(chan int)
go func() {
defer func() {
if recover() != nil {
// close(ch) panic occur
}
}()
ch <- 1
}()
直接捕获panic来判断已经关闭?这路子也太野了哈哈,不符合咱们程序员优雅的气质,咱们再思考别的
思考方式二:通过读channel 实现用 select 和 <-ch 来结合可以解决这个问题
ok的结果和含义:
true:读到数据,并且通道没有关闭。
false:通道关闭,无数据读到。
ch := make(chan int, 10)
go func() {
for i:=1;i<10;i++{
ch <- i
}
}()
time.Sleep(1*time.Second)
close(ch)
for i:=0;i<10;i++{
select {
case v, ok := <- ch:
if ok {
fmt.Println(v)
}else{
fmt.Println("关掉了")
}
default:
fmt.Println("没啥事")
}
}
需要注意:
1.case 的代码必须是 _, ok:= <- ch 的形式,如果仅仅是 <- ch 来判断,是错的逻辑,因为主要通过 ok的值来判断;
2.select 必须要有 default 分支,否则会阻塞函数,我们要保证一定能正常返回;
select {
case <-ctx.Done():
// ... exit
return
case v, ok := <-c:
// do something....
default:
// do default ....
}
当ctx.Done()中有值时,则判断channel已经退出。
但需要注意!!!select 的 case 一定要先判断 ctx.Done() 事件,否则很有可能先执行了 chan 的操作从而导致 panic 问题;
1.channel 并没有直接提供判断是否 close 的接口,官方推荐使用 context 和 select 语法配合使用,事件通知的方式,达到优雅判断 channel 关闭的效果;
2.channel 关闭姿势也有讲究,永远不要尝试在读端关闭,永远保持一个关闭入口处,使用 sync.WaitGroup 和 context 实现事件同步,达到优雅关闭效果;



