为何带缓冲的Channel存储值超缓冲大小仍未触发死锁?
Go带缓冲Channel的死锁认知误区解析
我正在学习Go语言的Channel,编写了两段预期会触发死锁的代码,但实际并未出现异常,想了解底层原理。根据我的理解,缓冲大小为2的Buffered channel,在无接收操作时最多只能存储2个值,否则会触发死锁,是不是我从一开始就存在认知误区?
第一段代码
c := make(chan int, 2) go func() { c <- 1 c <- 2 c <- 3 c <- 4 c <- 5 c <- 6 }() fmt.Println(<-c) ... fmt.Println(<-c)
我猜测可能是Channel填满2个值后,每次执行Println(<-c)接收值时会释放内存,从而让goroutine继续发送值。于是我编写了第二段代码试图阻止这种并发接收以触发死锁,但代码仍正常运行:
第二段代码
c := make(chan int, 2) go func() { c <- 1 c <- 2 c <- 3 c <- 4 c <- 5 c <- 6 }() fmt.Println(<-c, <-c, <-c, <-c, <-c, <-c)
核心疑问
为何这个Buffered channel看似能存储超过缓冲大小的值?若我对Channel的基础认知有误,能否推荐可靠的学习资源来纠正?
原理解析
你对带缓冲Channel的核心认知存在偏差:不是“无接收时最多存2个值就会触发死锁”,而是当缓冲区已满,且当前没有接收者在读取数据时,发送操作才会阻塞。
- 第一段代码中,goroutine先将前两个值送入缓冲区,此时缓冲区被填满,goroutine会阻塞在
c <- 3处。当主goroutine执行fmt.Println(<-c)接收一个值后,缓冲区腾出一个位置,发送goroutine的阻塞状态被解除,继续发送第三个值;之后重复“接收-释放缓冲区-继续发送”的过程,直到所有值发送完成,全程不会触发死锁。 - 第二段代码里,
fmt.Println的多个参数是从左到右逐个求值的。主goroutine每完成一次<-c接收,就会立即释放缓冲区的一个位置,让发送goroutine的阻塞点得以继续执行。也就是说,主goroutine接收第一个值时,缓冲区空出位置,发送goroutine就能发送第三个值;接收第二个值时,空出位置发送第四个值……整个过程中发送和接收交替推进,不会出现所有发送操作阻塞、接收也无法继续的死锁场景。
真正会触发死锁的场景,是所有goroutine都处于阻塞状态且无法推进。举个典型例子:
c := make(chan int, 2) c1 := make(chan int) go func() { c <- 1 c <- 2 c <- 3 // 缓冲区已满,此处阻塞 }() <-c1 // 主goroutine阻塞等待c1的值,无其他goroutine发送数据,所有goroutine均阻塞,触发死锁
学习资源推荐
- Go官方文档的Channel章节:权威可靠,清晰阐述了Channel的发送、接收规则,以及缓冲与无缓冲Channel的区别。
- 《Go程序设计语言》:业界公认的Go入门经典书籍,专门章节讲解Channel的设计原理与实践场景,搭配大量示例帮助建立正确认知。
- Go官方博客的Channel系列文章:深入解析Channel的底层实现机制,理解其运行逻辑。
内容的提问来源于stack exchange,提问作者bjensen
相关产品推荐
相关产品推荐

