面试的时候经常会遇到“从一个已经关闭的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"))
	}

	// ...
}

总结

操作已关闭的channelnil channel
如果channel中还有数据,可以继续读取;如果channel中没有数据了, 可以读到零值永久阻塞(deadlock)
panic: send on closed channel永久阻塞(deadlock)
closepanic: close of closed channelpanic: close of nil channel
本文使用 Zhihu On VSCode 创作并发布