Golang中Select添加Default分支为何触发死锁?
为什么Go语言Select语句添加Default分支会触发死锁?
问题重现
当在接收通道的Select语句中添加default分支时,代码运行会触发死锁:
package main import ( "sync" ) func main() { var wg sync.WaitGroup wg.Add(2) a := make(chan int) go func() { defer wg.Done() for { var ( name string x int ok bool ) select { case x, ok = <-a: name = "a" default: println("default error") } if !ok { return } println(name, x, ok) } }() go func() { defer wg.Done() defer close(a) for i := 0; i < 10; i++ { select { case a <- i: } } }() wg.Wait() }
运行输出:
a 0 true default error fatal error: all goroutines are asleep - deadlock!
移除default分支后,代码运行完全正常:
package main import ( "sync" ) func main() { var wg sync.WaitGroup wg.Add(2) a := make(chan int) go func() { defer wg.Done() for { var ( name string x int ok bool ) select { case x, ok = <-a: name = "a" } if !ok { return } println(name, x, ok) } }() go func() { defer wg.Done() defer close(a) for i := 0; i < 10; i++ { select { case a <- i: } } }() wg.Wait() }
运行输出:
a 0 true a 1 true a 2 true a 3 true a 4 true a 5 true a 6 true a 7 true a 8 true a 9 true
死锁原因分析
- 无缓冲通道的核心特性:代码中创建的
a是无缓冲通道,这类通道的发送和接收操作必须配对执行——发送方会阻塞直到有接收方准备好接收数据,接收方也会阻塞直到通道中有数据可接收。 - 带default的Select行为:当Select语句包含
default分支时,如果所有case对应的通道操作都无法立即执行,会立刻执行default分支,不会阻塞等待。 - 死锁的具体触发流程:
- 发送方发送第一个值
0,接收方正常接收并打印,此时通道回到空状态。 - 接收方再次进入循环,Select检测到通道无数据可接收,直接执行
default分支打印"default error",由于ok仍然是true(没有接收到通道关闭的信号),循环会继续执行。 - 此时发送方尝试发送下一个值
1,但无缓冲通道需要接收方处于等待接收的状态才能完成发送,而接收方一直在循环执行default分支,根本没有在等待接收数据,发送方因此陷入阻塞。 - 接收方不断重复执行
default分支,永远不会退出循环;发送方阻塞在发送操作上;主goroutine阻塞在wg.Wait()上。所有goroutine都进入无法推进的阻塞状态,最终触发死锁。
- 发送方发送第一个值
- 移除default后的正常逻辑:接收方的Select没有
default分支,会一直阻塞在case x, ok = <-a:上等待数据。发送方发送数据时,接收方会被唤醒处理数据,发送完成后继续下一次发送,直到所有10个值发送完毕,发送方关闭通道。接收方收到通道关闭的信号(ok=false)后退出循环,所有goroutine正常结束,不会触发死锁。
内容的提问来源于stack exchange,提问作者Syuan-Cheng
相关产品推荐
相关产品推荐

