go 10行代码踩遍channel的3个panic
Posted historyofsmile
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go 10行代码踩遍channel的3个panic相关的知识,希望对你有一定的参考价值。
go 10行代码踩遍channel的3个panic
0、nil channel的阻塞
开发的时候,由于疏忽,没有初始化channel,写出如下代码:
func main() {
var ch chan int // nil channel
go func() {
ch <- 123 // 阻塞
}()
fmt.Println(<-ch) // 阻塞
}
开始执行,发现程序完全阻塞,除此之外,没有任何其他异常——读写 nil channel
都会阻塞,不会panic。
尝试一下关闭这个channel会发生什么。
1、关闭一个 nil channel —— panic
在上面的代码中添加一个关闭channel的函数:
func main() {
var ch chan int // nil channel
close(ch) // panic: close of nil channel /*1*/
go func() {
ch <- 123
}()
fmt.Println(<-ch)
}
这一个close操作,直接操作出个panic,好吓人,仔细一看,哦,原来我们的channel只是定义了,并没有初始化,它只是个nil channel,那么可知:close 一个 nil channel 会报panic: close of nil channel
。
如果我们把这个channel 初始化了再关闭呢?它应该就不会报这个panic了吧,说干就干。
2、向一个closed channel 发送数据 —— panic
在上一步的基础上,给channel 做了个初始化操作,如下:
func main() {
var ch chan int
ch = make(chan int) /*2*/
close(ch) // closed /*1*/
go func() {
ch <- 123 // panic: send on closed channel
}()
fmt.Println(<-ch) // 这里仍然可以读取到数据,为int的默认值 —— closed 的channel 可以读取到数据(默认值)
}
surprise!主函数执行完了,那么意味着channel中读到了值,但是读到的并不是我们写入的123
,而是0
。
但是,同时,程序抛出一个**panic: send on closed channel
。可见,往一个 已经关闭的channel中写数据,直接会panic** 。
接下来,由于手滑,close(ch)
被我多复制了一行,你猜怎么着?
3、关闭一个已经关闭的channel —— panic
func main() {
var ch chan int
ch = make(chan int) /*2*/
close(ch) /*1*/
close(ch) // panic: close of closed channel /*3*/
go func() {
ch <- 123
}()
fmt.Println(<-ch)
}
这下倒好,程序根本就执行不到末尾,在第二个 close(ch)
处就panic了——panic: close of closed channel
,可见,关闭已经被关闭的channel会panic。
这一步操作纯属失误,我申请悔棋,代码回退到步骤2,那里至少有机会把函数执行完不是吗?
4、在一个合适的时机关闭channel(正确示范)
经过上面的一系列操作,你会发现,这个程序中,你即便是不关闭这个channel,程序也能无伤地执行完。例如:
func main() {
var ch chan int
ch = make(chan int)
go func() {
ch <- 123
}()
fmt.Println(<-ch) // 123
}
但是,作为一个负责人的gopher,总觉得这么处理有些不妥当,平时写goroutine
的原则是:谁负责创建,谁就要负责释放。使用channel也应该养成类似的习惯,最好能自己动手关掉它。
例如,在channel使用完成的时候关闭它:
func main() {
var ch chan int
ch = make(chan int)
defer close(ch) /*4*/
go func() {
ch <- 123
}()
fmt.Println(<-ch) // 123
}
或者在channel数据发送完的时候。我们知道关闭掉channel,并不影响接收数据,那么发送完成后就关闭掉它,安全又妥当。例如:
func main() {
var ch chan int
ch = make(chan int)
go func() {
defer close(ch) /*4'*/
ch <- 123
}()
fmt.Println(<-ch) // 123
}
总结
channel 的panic
channel的panic有3中情形,两个和close()操作有关,两个和closed状态有关。so,操作channel的时候一定要注意关于关闭(的)
操作。
- **panic: close of nil channel **
- panic: send on closed channel
- panic: close of closed channel
nil channel
刚开就交代了nil channel 的情况,无论发送还是接收,都会阻塞,切记使用channel要make
初始化,如果遗漏了这个操作,排查阻塞可不像panic那么容易。
接收closed channel
- 已经关闭的channel仍然可以接收,这也是channel的另一个妙用,可以用它作为1:N的通知器,例如:
func main(){
ch:=make(chan struct{})
for i:=0;i<5;i++{
go func(i int) {
<-ch // goroutine 阻塞在此,等待信号,一起向下执行
fmt.Println(i," do something ...")
}(i )
}
close(ch) // 关闭 channel,通知goroutine都向下执行
time.Sleep(time.Second)
}
- close()操作也是 range操作的终止信号:
func main() {
ch := make(chan int)
go func() {
defer close(ch) // 数据发送完之后,就关闭channel
for i := 0; i < 5; i++ {
ch <- i
}
}()
// channel 也有类似断言一样的操作,第二个值为当前channel的状态,如果已经关闭,则为false
v, ok := <-ch
if ok {
fmt.Println("v, ok := <-ch ---> ",v)
} else {
return
}
// 数据接收到 close 时刻的,就不在循环获取,退出range
for value := range ch {
fmt.Println("range ---> ",value)
}
}
结果如下:
v, ok := <-ch ---> 0
range ---> 1
range ---> 2
range ---> 3
range ---> 4
以上是关于go 10行代码踩遍channel的3个panic的主要内容,如果未能解决你的问题,请参考以下文章
Go36-21,22-panic函数recover函数以及defer语句
2022-10-16:以下go语言代码输出什么?A:timed out;B:panic;C:没有任何输出。 package main import ( “context“ “fmt“