为何channel阻塞时,带default的select会触发goroutine死锁报错
问题根本原因拆解
核心认知偏差点
你对通道类型的行为判断存在两个关键误区,直接导致了执行结果和预期不符:
- 混淆了无缓冲通道和“写满的有缓冲通道”的本质差异
- 代码中
c := make(chan int)创建的是无缓冲通道,通道容量为0,从创建起就没有任何可存储值的空间,不存在“写满”这个状态前提。 - 你认知中“通道写满触发发送阻塞”的场景,仅适用于有缓冲通道:当通道内已存值的数量等于初始化时指定的容量,后续发送操作才会进入阻塞。
- 代码中
- 误判了前置独立发送语句的执行逻辑
- 无缓冲通道的发送操作必须匹配到同时执行的接收操作才能完成,没有接收者的前提下,独立的发送语句本身就会陷入永久阻塞,根本不会流转到后续的select代码块。
实际执行链路还原
你写的代码根本没机会执行到select分支判断环节,完整流程如下:
- 初始化创建无缓冲通道
c - 执行
c <- 1:尝试向无缓冲通道发送值1,此时全程序没有任何其他goroutine在等待接收这个通道的值,主goroutine直接在这行陷入永久阻塞 - Go运行时检测到当前唯一的主goroutine处于休眠状态,没有任何可唤醒它的执行逻辑,直接抛出
所有goroutine均处于休眠状态——死锁!的错误
select default分支的触发逻辑澄清
你对select的行为判断本身没有错误:当select代码块顺利执行到判断逻辑时,所有case对应的通道操作都无法立刻完成,就会触发default分支。
但这个逻辑生效的前提是,select本身能被执行到。你的场景中前置的独立发送语句已经把主goroutine卡死,根本走不到select的判断环节,自然不可能命中default分支。
符合你预期效果的对照实现
如果要复现“通道写满、select发送阻塞、命中default分支”的逻辑,需要显式创建有缓冲通道,预先将通道占满:
func main() { // 创建容量为1的有缓冲通道 c := make(chan int, 1) // 存入1个值,此时通道容量占满,无剩余存储空间 c <- 1 select { case c <- 5: fmt.Println("a") default: // 通道已占满,发送操作无法立刻完成,命中default分支 fmt.Println("only case not blocked") } }
内容的提问来源于stack exchange,提问作者J Bailey
相关产品推荐
相关产品推荐

