为何移除merge函数中包裹wg.Wait()的goroutine会引发Go死锁?
Go管道merge函数移除wg.Wait()协程后死锁原因分析
问题描述
我阅读了Go官方博客的管道文章,尝试移除包裹
wg.Wait()的goroutine后,出现了所有goroutine休眠的死锁问题,无法理解其中原因,原代码如下:
func merge(cs ...<-chan int) <-chan int { var wg sync.WaitGroup out := make(chan int) // Start an output goroutine for each input channel in cs. output // copies values from c to out until c is closed, then calls wg.Done. output := func(c <-chan int) { for n := range c { out <- n } wg.Done() } wg.Add(len(cs)) for _, c := range cs { go output(c) } // Start a goroutine to close out once all the output goroutines are // done. This must start after the wg.Add call. go func() { wg.Wait() close(out) }() return out }
死锁原因解析
当你移除包裹wg.Wait()的协程,直接在merge函数内调用wg.Wait()时,会形成一个死锁闭环:
- merge函数阻塞:merge启动所有output协程后,调用
wg.Wait()会让当前执行merge的goroutine停在这里,必须等所有output协程执行wg.Done()才能继续。 - output协程阻塞:每个output协程在
out <- n处卡住——因为out是无缓冲通道,发送操作必须等到有接收方才能完成。但此时merge还没返回out通道,调用merge的代码还没开始接收数据,所以output协程永远无法走到wg.Done()这一步。 - 互相等待形成死锁:merge等output协程完成,output协程等
out有接收方,接收方等merge返回out通道,所有goroutine都陷入休眠,触发死锁。
原代码把wg.Wait()放在单独协程里的核心作用,就是让merge函数能立刻返回out通道,让接收方可以开始接收数据,output协程的发送操作不会阻塞,完成后调用wg.Done(),最后那个独立协程等所有output完成后关闭out,整个流程就能正常推进。
内容的提问来源于stack exchange,提问作者Thai Nguyen
相关产品推荐
相关产品推荐

