本文同时参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
前文回顾
所以目前我们发现
- 错误的超时机制的设计让我们没办法正确运行
- 为了做出改变,我们希望最好有确定court通道是否关闭的办法
所以本章我们主要讨论如何确定court通道是否关闭,在此之前,我们不妨设想下,没有这个功能会给我造成什么样子的麻烦
-
在不能更改channel状态的情况下,没有简单普遍的方式来检查channel是否已经关闭了
-
关闭已经关闭的channel会导致panic,在不知道channel是否已经关闭的情况下去关闭channel是很危险的
-
发送值到已经关闭的channel会导致panic,在不知道channel是否已经关闭的情况下去向channel发送值也是很危险的
读取数据时
每次从通道读取数据时,都可以读到通道本身是否关闭的状态,因此在读取数据时,我们可以轻松判断court通道是否关闭。
package main
import (
"fmt"
)
func main() {
c := make(chan int, 10)
c <- 1
c <- 2
c <- 3
close(c)
for {
i, isClose := <-c
if !isClose {
fmt.Println("channel closed!")
break
}
fmt.Println(i)
}
}
写入数据时
我们期待这类似的功能,一个简单的函数让我们可以监测一个通道的状态
func main() {
c := make(chan T)
fmt.Println(IsClosed(c)) // false
c <- DATA
close(c)
fmt.Println(IsClosed(c)) // true
}
然而这中isClosed函数往往作用不大,(这也是为什么不会有这类函数的原因),因为就算有一个简单的 closed(chan T) bool
函数来检查channel是否已经关闭,它的用处还是很有限的,就像内置的len
函数用来检查缓冲channel中元素数量一样。原因就在于,已经检查过的channel的状态有可能在调用了类似的方法返回之后就修改了,因此返回来的值已经不能够反映刚才检查的channel的当前状态了。
尽管在调用closed(ch)
返回true
的情况下停止向channel发送值是可以的,但是如果调用closed(ch)
返回false
,那么关闭channel或者继续向channel发送值就不安全了(这样可能会造成panic,因为之前未关闭的通道可能会在此刻恰好关闭)。
使用通道的原则
事实上,go并不建议我们由接受者关闭通道,或者在有多个发送者时由其中一个发送者关闭通道,维持这条原则将保证永远不会发生向一个已经关闭的channel发送值或者关闭一个已经关闭的channel。
那么如何安全的打破这条原则?
我们可以使用panic/recover()模式来向通道中写入数据:
func SafeWrite(ch chan T, value T) (isClosed bool) {
defer func() {
if recover() != nil {
// 在panic后,会进入这个if中,然后将返回值改变为true,代表通道是关闭的
isClosed = true
}
}()
ch <- value // 如果通道关闭,这里会报panic
return false // 如果可以正常写入,这里会返回false,代表通道不是关闭的
}