我正在尝试重现一个问题,并使用以下代码找到了最小用例。如果我关闭所有通道(绕过 i == 0 测试),一切都会按预期进行。Wg 状态递减并触发完成,主要退出正常。当我跳过关闭这些通道之一(故意)时,我希望主例程等待,而在这种情况下等待组信号量将无限期阻塞。相反,我收到了一个错误:“致命错误:所有 goroutine 都睡着了 - 死锁!”。这是为什么?我一定错过了一些基本的东西,还是运行时过于热心了?package mainimport ( "fmt" "sync")const N int = 4func main() { done := make(chan struct{}) defer close(done) fmt.Println("Beginning...") chans := make([]chan int, N) var wg sync.WaitGroup for i := 0; i < N; i++ { wg.Add(1) chans[i] = make(chan int) go func(i int) { // p0 defer wg.Done() for m := range chans[i] { fmt.Println("Received ", m) } fmt.Println("Ending p", i) }(i) } go func() { wg.Wait() done <- struct{}{} // signal main that we are done }() for i := 0; i < N; i++ { fmt.Println("Closing c", i) if i != 0 { // Skip #0 so wg doesn't reach '0' close(chans[i]) } } <-done // wait to receive signal from anonymous join function fmt.Println("Ending.")}更新:我编辑了代码以避免竞争条件。仍然收到此错误。if i != 0之所以存在,是因为它是故意的。我希望 wg.Wait 永远阻塞(其信号量永远不会达到 0。)为什么我不能这样做?这似乎与我在其他地方<-done没有匹配的情况下使用done <- struct{}{}一样。在这种情况下编译器也会抱怨吗?
2 回答
小怪兽爱吃肉
TA贡献1852条经验 获得超1个赞
这是发生了什么:
第一个
go func(i int) {
goroutine 没有退出,因为chans[0]
没有关闭。因为 goroutine 没有退出,
wg.Done
也没有被调用。wg.Wait()
由于上一点,对永远阻塞的调用。Main 永远阻塞,因为信号没有发送到
done
。
您可以通过删除 来解决死锁if i != 0 {
,但还有另一个问题。等待组有一场比赛。有可能在调用 wg.Add(1) 之前调用 wg.Done()。在启动 goroutine 之前调用 wg.Add() 以避免竞争。
守着一只汪
TA贡献1872条经验 获得超3个赞
if
你的 for 循环中的语句不会让最后一个通道关闭,所以你goroutine
要等待发生的事情,chans[i]
这将阻止defer wg.Done()
永远发生的事情,这反过来永远不会让wg.Wait()
完成,THENNNNN 永远不会让done <- struct{}{}
得到信号
简而言之,您if statement
的循环并没有关闭最后一个通道并导致死锁,因为没有人无能为力。
正如@CodingPickle 确实指出的那样,将您移动wg.Add(1)
到您的开头for loop
以防止任何竞争条件
http://play.golang.org/p/j1D5LZGUhd
- 2 回答
- 0 关注
- 178 浏览
添加回答
举报
0/150
提交
取消