为何Go中用go fmt.Println(<-c)读取通道会引发死锁?
无缓冲通道引发死锁的原因分析
先看你的代码:
package main import "fmt" func main() { c := make(chan int) go fmt.Println(<-c) c <- 32 }
死锁的核心在于无缓冲通道的发送和接收必须同时配对完成,缺少任意一方都会触发阻塞。
具体执行流程如下:
- 主goroutine创建了一个无缓冲通道
c。 - 你启动了新goroutine,但Go的goroutine调度由runtime自主决定,这个新goroutine大概率还没来得及被调度执行,也就没进入等待接收的状态。
- 主goroutine紧接着执行
c <- 32,此时没有任何goroutine在等待接收该通道的值,无缓冲通道的发送操作会直接阻塞主goroutine。 - 主goroutine阻塞后,runtime没有机会调度那个新的goroutine执行接收操作,最终所有goroutine都卡在阻塞状态,Go runtime检测到这种情况就会抛出死锁错误。
两种可行的修复方案
- 改用带缓冲的通道:给通道设置至少1个缓冲容量,发送操作可以先把值存入缓冲区,不用立刻等待接收方,runtime有足够时间调度新goroutine去接收:
c := make(chan int, 1) // 带1个缓冲的通道 go fmt.Println(<-c) c <- 32
- 确保接收goroutine先启动:用
sync.WaitGroup同步,让主goroutine等新goroutine准备好再发送,避免调度顺序问题:
package main import ( "fmt" "sync" ) func main() { c := make(chan int) var wg sync.WaitGroup wg.Add(1) go func() { wg.Done() // 标记goroutine已启动 fmt.Println(<-c) }() wg.Wait() // 等待goroutine启动完成 c <- 32 }
内容的提问来源于stack exchange,提问作者Тимур Имашев
相关产品推荐
相关产品推荐

