为何此案例中主goroutine会阻塞进而引发死锁?
Go代码死锁原因解析
问题描述
以下Go代码执行时会触发死锁,主goroutine被阻塞:
package main import "fmt" func square(numbers chan int, squares chan int) { for n := range numbers { squares <- n * n } close(squares) } func main() { numbers := make(chan int) squares := make(chan int) go square(numbers, squares) for i := 0; i < 10; i++ { numbers <- i } close(numbers) for s := range squares { fmt.Println(s) } }
已知将向numbers通道发送数据的逻辑抽离到单独goroutine中可以解决问题,示例代码如下:
go func() { for i := 0; i < 10; i++ { numbers <- i } close(numbers) }()
但存在疑问:原代码里,主goroutine第一次向numbers发送数据被阻塞后,调度器难道不会启动square goroutine,让两者来回通信吗?为什么会产生死锁?
死锁原因解析
核心问题出在无缓冲通道的阻塞特性和主goroutine的执行逻辑上:
- 你创建的
numbers和squares都是无缓冲通道,无缓冲通道的发送操作必须等对应的接收操作完成才能继续,接收操作也必须等发送操作完成才能推进。 - 主goroutine启动
squaregoroutine后,立刻进入循环向numbers发数据。此时squaregoroutine可能还没被调度器执行(Go调度器不保证goroutine的执行顺序),主goroutine执行numbers <- 0时,因为没有接收方,直接被阻塞。 - 就算
squaregoroutine被调度起来,它从numbers拿到0后,会执行squares <- 0——但squares也是无缓冲通道,此时主goroutine还卡在numbers的发送操作上,根本没开始遍历squares接收数据,所以squaregoroutine也会被阻塞在squares的发送步骤上。 - 到这一步,两个goroutine彻底卡死:主goroutine等有人接
numbers的数据,squaregoroutine等有人接squares的数据,谁都没法推进,最终触发死锁。
而把发送numbers的逻辑放到单独goroutine后:
- 新goroutine负责发数据到
numbers,主goroutine可以立刻开始处理squares的接收。 - 当新goroutine发数据被阻塞时,主goroutine已经在接收
squares的数据,squaregoroutine的发送操作能得到响应,三个goroutine形成正常的通信流转,不会出现互相堵死的情况。
内容的提问来源于stack exchange,提问作者Don Draper
相关产品推荐
相关产品推荐

