You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何带缓冲的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 07:54:26