面试的时候经常会遇到“从一个已经关闭的channel里面读取数据会发生什么?”这种类似的问题,我自己对于closed channel 和 nil channel 一直处于混淆不清的状态,因此做了一系列实验进行验证,整理了一下发在这里。
已关闭的channel
读
func testRead() {
ch := make(chan int, 10)
ch <- 1
ch <- 2
ch <- 3
ch <- 4
close(ch)
num := <-ch
fmt.Println("read: ", num)
for num = range ch {
fmt.Println("read: ", num)
}
}
输出:
read: 1
read: 2
read: 3
read: 4
应该说明的是,即便一个channel里面没有数据了,我们还是可以读到零值,比如下面这段程序:
func testRead() {
ch := make(chan int, 10)
ch <- 1
ch <- 2
ch <- 3
ch <- 4
close(ch)
for num := range ch {
fmt.Println("read: ", num)
}
num := <-ch
fmt.Println("read: ", num)
}
输出:
read: 1
read: 2
read: 3
read: 4
read: 0
那么,如果channel里面确实有0,我们怎么知道channel有没有被close呢?很简单,读取channel的时候其实返回了两个值,第二个值如果为false,说明channel已经关闭了:
func testRead() {
ch := make(chan int, 10)
ch <- 1
close(ch)
num, ok := <-ch
if ok {
fmt.Println("read: ", num)
}
num, ok = <-ch
if !ok {
fmt.Println("read from closed channel: ", num)
}
}
输出:
read: 1
read from closed channel: 0
写
向一个已经被关闭的channel写入数据,会引发panic。
func testWrite() {
ch := make(chan int ,10)
close(ch)
ch <- 1
}
输出:
panic: send on closed channel
可以看下源码:
// src/runtime/chan.go
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
// ...
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
// ...
}
close
关闭一个已经被关闭的channel,将会引发panic。
func testClose() {
ch := make(chan int , 10)
fmt.Println("first close: ")
close(ch)
fmt.Println("second close: ")
close(ch)
}
输出:
first close:
second close:
panic: close of closed channel
源码中相关的代码片段:
// src/runtime/chan.go
func closechan(c *hchan) {
// ...
lock(&c.lock)
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("close of closed channel"))
}
// ...
}
nil channel
读
从一个nil channel读数据,程序将会永久阻塞。
func testReadNil() {
var ch chan int
<-ch
}
输出:
fatal error: all goroutines are asleep - deadlock!
写
向一个nil channel写数据,程序将会永久阻塞。
func testWriteNil() {
var ch chan int
ch <- 1
}
输出:
fatal error: all goroutines are asleep - deadlock!
close
关闭一个nil channel,将会引发panic。
func testCloseNil() {
var ch chan int
close(ch)
}
输出:
panic: close of nil channel
源码中相关的代码片段:
// src/runtime/chan.go
func closechan(c *hchan) {
if c == nil {
panic(plainError("close of nil channel"))
}
// ...
}
总结
操作 | 已关闭的channel | nil channel |
---|---|---|
读 | 如果channel中还有数据,可以继续读取;如果channel中没有数据了, 可以读到零值 | 永久阻塞(deadlock) |
写 | panic: send on closed channel | 永久阻塞(deadlock) |
close | panic: close of closed channel | panic: close of nil channel |
本文使用 Zhihu On VSCode 创作并发布