面试的时候经常会遇到“从一个已经关闭的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 创作并发布