go channel close 后再读取会发生什么?

go channel close 后再读取会发生什么?

这可能是日常开发的时候不会仔细思考的问题,今天看到了感觉非常有趣、决定好好的研究一下。实践是检验真理的唯一标准。

ch := make(chan string, 10)
ch <- "2333"
ch <- "2333"
str := <-ch
fmt.Println("<-ch", str)
close(ch)
str = <-ch
fmt.Println("<-ch", str)
str = <-ch
fmt.Println("<-ch", str)

输出

<-ch 2333
<-ch 2333
<-ch

我们可以看到channel在被关闭后是可以继续读取的。即使数据被全部读取完后,仍然会零值返回。这时候有人说了「channel读取不是有第二个参数返回吗?」会发生什么呢?让我们继续测试下。

ch := make(chan string, 10)
ch <- "2333"
ch <- "2333"
str := <-ch
fmt.Println("<-ch", str)
close(ch)
str, ok := <-ch
fmt.Println("<-ch", str, "ok:", ok)
str, ok = <-ch
fmt.Println("<-ch", str, "ok:", ok)
str, ok = <-ch
fmt.Println("<-ch", str, "ok:", ok)

输出

<-ch 2333
<-ch 2333 ok: true
<-ch  ok: false
<-ch  ok: false

可以看到,即使通道关闭了,ok 变量仍然在channel有值返回的情况下返回true。所以并不能通过第二个变量来判断通道是否关闭。这个返回值只能用来判断通道内是否还有没有数据可以读取。

当然我们还可以通过range遍历通道。当channel被关闭后。range会自动结束并退出遍历。

ch := make(chan string, 10)
ch <- "2333"
ch <- "2333"
str := <-ch
fmt.Println("<-ch", str)
close(ch)
for str := range ch {
    fmt.Println("<-ch", str)
}
fmt.Println("finished")

输出

<-ch 2333
<-ch 2333
finished

特别要注意的是当我们使用range遍历通道的时候、当通道不再有数据写入后应当关闭通道。否则会造成死锁的情况。

fatal error: all goroutines are asleep - deadlock!

今天问题可以引申一个新的问题,当channel关闭到底做了哪些操作呢?什么时候才是真正的把channel关闭了呢?在我查找资料的时候看到了下面的文章。值得深入学习一下。可以让我们更好的理解channel。

延展阅读

Go 最细节篇 — chan 为啥没有判断 close 的接口 ?
golang chan 最详细原理剖析,全面源码分析!看完不可能不懂的!
Go 最细节篇
Golang 技术专辑