WaitGroup – 致命错误:所有的 goroutine 都处于休眠状态 – 死锁

huangapple go评论82阅读模式
英文:

WaitGroup - fatal error: all goroutines are asleep - deadlock

问题

有人能解释一下为什么这段代码会抛出一个"fatal error: all goroutines are asleep - deadlock!"的错误吗?

我找不到问题出在哪里。我看到一些关于这个特定错误的问题,但原因大多是在没有关闭通道的情况下循环遍历通道。
谢谢!

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {

	ch := make(chan time.Duration)

	var wg sync.WaitGroup

	for _, v := range []time.Duration{5, 1} {
		wg.Add(1)
		go wait(v, ch, wg)
		fmt.Println(<-ch)
	}

	wg.Wait()
}

func wait(seconds time.Duration, c chan time.Duration, wg sync.WaitGroup) {

	defer wg.Done()

	time.Sleep(seconds * time.Second)

	c <- seconds
}

这段代码中的问题在于wait函数的参数wg sync.WaitGroup是按值传递的,而不是按引用传递。这意味着在wait函数中对wg的修改不会影响到main函数中的wg。因此,wg.Wait()在等待的时候,wait函数中的wg.Done()没有被调用,导致所有的goroutine都无法退出,最终引发了死锁错误。

要解决这个问题,你可以将wg sync.WaitGroup作为指针传递给wait函数,这样wait函数中对wg的修改就会影响到main函数中的wg。修改后的代码如下:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {

	ch := make(chan time.Duration)

	var wg sync.WaitGroup

	for _, v := range []time.Duration{5, 1} {
		wg.Add(1)
		go wait(v, ch, &wg)
		fmt.Println(<-ch)
	}

	wg.Wait()
}

func wait(seconds time.Duration, c chan time.Duration, wg *sync.WaitGroup) {

	defer wg.Done()

	time.Sleep(seconds * time.Second)

	c <- seconds
}

这样修改后,代码就不会再抛出死锁错误了。

英文:

Can someone explain why this code throws an "fatal error: all goroutines are asleep - deadlock!"?

I can't seem to find what is wrong. I've seen some questions about this specific error, but the reason was mostly looping through a channel without closing it.
Thank you!

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

func main() {

	ch := make(chan time.Duration)

	var wg sync.WaitGroup

	for _, v := range []time.Duration{5, 1} {
		wg.Add(1)
		go wait(v, ch, wg)
		fmt.Println(&lt;-ch)
	}

	wg.Wait()
}

func wait(seconds time.Duration, c chan time.Duration, wg sync.WaitGroup) {

	defer wg.Done()

	time.Sleep(seconds * time.Second)

	c &lt;- seconds
}

答案1

得分: 4

你需要通过引用而不是值传递WaitGroup。否则,Done方法将没有效果。该类型的文档中有如下说明:

> 在首次使用后,WaitGroup不能被复制。

将你的代码修复如下,它将正常工作:

func main() {

    ch := make(chan time.Duration)

    var wg sync.WaitGroup

    for _, v := range []time.Duration{5, 1} {
        wg.Add(1)
        go wait(v, ch, &wg)
        fmt.Println(<-ch)
    }

    wg.Wait()
}

func wait(seconds time.Duration, c chan time.Duration, wg *sync.WaitGroup) {

    defer wg.Done()

    time.Sleep(seconds * time.Second)

    c <- seconds
}

此外,还可以将此模式表达如下:

func main() {
    ch := make(chan time.Duration)

    var wg sync.WaitGroup

    for _, v := range []time.Duration{5, 1} {
        wg.Add(1)

        go func() {
            defer wg.Done()
            wait(v, ch)
        }()
        fmt.Println(<-ch)
    }

    wg.Wait()
}

func wait(seconds time.Duration, c chan time.Duration) {
    time.Sleep(seconds * time.Second)
    c <- seconds
}

这种情况下的好处是,wait函数不需要意识到任何等待组(它可以是第三方函数),并且不会对传递等待组的方式(值传递还是引用传递)产生混淆。

英文:

You have to pass the WaitGroup by reference, not by value. Otherwise the Done has no effect. The documentation of this type says:

> A WaitGroup must not be copied after first use.

Fix your code to this and it will work:

func main() {

	ch := make(chan time.Duration)

	var wg sync.WaitGroup

	for _, v := range []time.Duration{5, 1} {
		wg.Add(1)
		go wait(v, ch, &amp;wg)
		fmt.Println(&lt;-ch)
	}

	wg.Wait()
}

func wait(seconds time.Duration, c chan time.Duration, wg *sync.WaitGroup) {

	defer wg.Done()

	time.Sleep(seconds * time.Second)

	c &lt;- seconds
}

It is also common to express this pattern as follows:

func main() {
	ch := make(chan time.Duration)

	var wg sync.WaitGroup

	for _, v := range []time.Duration{5, 1} {
		wg.Add(1)

		go func() {
			defer wg.Done()
			wait(v, ch)
		}()
		fmt.Println(&lt;-ch)
	}

	wg.Wait()
}

func wait(seconds time.Duration, c chan time.Duration) {
	time.Sleep(seconds * time.Second)
	c &lt;- seconds
}

What's nice about this case is that wait doesn't have to be aware of any wait groups (it could be a 3rd-party function, for example), and there's no confusion about passing a wait group by value or reference.

huangapple
  • 本文由 发表于 2021年6月25日 02:12:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/68120818.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定