go关闭的chan是否会阻塞

如果在一个 goroutine 中关闭了 channel,另一个协程对 channel 的读取操作是否会被阻塞?

问题验证

首先,通过代码验证是否会阻塞。验证代码如下:

func main() {
	ch := make(chan struct{})
	go func() {
		log.Printf("before closing")
		close(ch)
		log.Printf("after closing")
	}()
	<-ch
	log.Printf("after receiving")
}

输出如下:

2022/07/02 15:36:07 before closing
2022/07/02 15:36:07 after closing
2022/07/02 15:36:07 after receiving

结果分析:

如果ch不管close与否都会被阻塞,则 gorountine 中close之后,receiving日志也不会进行打印,而是会一直等待ch发送数据,与实验结果不符。

查看 channel 实现

go 源码中,对 chan 定义如下(runtime.hchan):

type hchan struct {
	qcount   uint           // total data in the queue
	dataqsiz uint           // size of the circular queue
	buf      unsafe.Pointer // points to an array of dataqsiz elements
	elemsize uint16
	closed   uint32
	elemtype *_type // element type
	sendx    uint   // send index
	recvx    uint   // receive index
	recvq    waitq  // list of recv waiters
	sendq    waitq  // list of send waiters

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex
}

runtime/chan.go文件中,当 chan 接受信息时,使用的函数(func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool))中,有对closed标志位的判断,如下:

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
	if c == nil {
		if !block {
			return
		}
		gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}

	lock(&c.lock)

	if c.closed != 0 && c.qcount == 0 {
		unlock(&c.lock)
		if ep != nil {
			typedmemclr(c.elemtype, ep)
		}
		return true, false
	}

由此可见,close后的 chan,读写操作是不会被阻塞的。

参考链接

  1. Go 语言设计与实现
  2. go 源码