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

为何这段Go代码未产生死锁?关于无缓冲通道的疑问

Go无缓冲通道的死锁疑惑与解析

测试代码

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func main() {
    ch := make(chan int) // 把通道声明在main()里

    wg.Add(4)
    go sender(ch, 42)
    go receiver(ch)

    go sender(ch, 7)
    go receiver(ch)

    wg.Wait()
}

func receiver(ch <-chan int) {
    time.Sleep(5 * time.Second)
    i := <-ch
    fmt.Printf("RECEIVING %v \n", i)
    wg.Done()
}

func sender(ch chan<- int, i int) {
    ch <- i
    wg.Done()
}

我的认知与疑惑

我对Go无缓冲通道的现有认知:

  • 无缓冲通道的发送方会阻塞,直到接收方读取通道数据;
  • 如果发送方在接收方读取第一条数据前尝试发送另一条消息,会无限阻塞进而导致死锁。

请问这个认知是否有误?

我原本认为上面的代码会触发死锁,理由是:
启动了2个sender goroutine(sender(ch, 42)和sender(ch,7))与2个receiver goroutine;每个sender会立即向无缓冲通道发值,但每个receiver要先等5秒才会读取通道。按逻辑,sender会因为没人接收而阻塞,最终引发死锁,但实际运行并没有死锁,想知道原因。

另外,我用下面的代码能复现死锁:

wg.Add(3)
go sender(ch, 42)
go receiver(ch)
go sender(ch, 7)

问题解析

你的核心认知前半部分是对的,但后半部分有偏差——无缓冲通道的发送阻塞是针对单个发送操作的,只要有对应数量的接收操作最终会被执行,就不会死锁。

为什么第一段代码没触发死锁

你启动了2个sender和2个receiver,虽然receiver先睡5秒,但它们最终都会执行<-ch的接收操作。Go调度器会把阻塞的sender goroutine挂起,等5秒后receiver醒来执行接收时,每个sender的阻塞都会被对应的接收解除:一个receiver接收42,另一个接收7,所有goroutine都能完成wg.Done(),main里的wg.Wait()也能正常结束,自然不会死锁。

为什么第二段代码会触发死锁

这里是2个sender和1个receiver。第一个sender发送42,会阻塞到receiver醒来接收;但第二个sender发送7时,此时没有第二个receiver来接收它——唯一的receiver还在睡觉,就算等它醒来,接收完42后也没有后续的接收操作了。这个第二个sender会一直阻塞,永远无法执行wg.Done(),而wg.Add(3)要求3个Done才能结束,main的wg.Wait()会一直等,最终整个程序所有goroutine都卡住,触发死锁。

总结:无缓冲通道的发送阻塞,只要发送操作的数量 ≤ 接收操作的数量(且接收操作最终会被执行),就不会死锁;只有当发送操作比接收操作多,或者没有对应的接收操作时,才会出现永久阻塞进而死锁。

内容的提问来源于stack exchange,提问作者Shankhashuvro Sinha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 14:40:07