sync.Waitgroup不会阻止执行

zde*_*bra 0 go

请考虑此代码段

package main

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

func main() {
    wg := new(sync.WaitGroup)
    nap := func() {
        wg.Add(1)
        time.Sleep(2 * time.Second)
        fmt.Println("nap done")
        wg.Done()
    }

    go nap()
    go nap()
    go nap()

    fmt.Println("nap time")
    wg.Wait()
    fmt.Println("all done")
}
Run Code Online (Sandbox Code Playgroud)

运行此类代码可获得预期输出:

nap time
nap done
nap done
nap done
all done
Run Code Online (Sandbox Code Playgroud)

现在让我们先省略第一个标准输出打印wg.Wait():

// fmt.Println("nap time")
wg.Wait()
fmt.Println("all done")
Run Code Online (Sandbox Code Playgroud)

输出现在变为意外:

all done
Run Code Online (Sandbox Code Playgroud)

预期的地方是:

nap done
nap done
nap done
all done
Run Code Online (Sandbox Code Playgroud)

操场上的相同代码确实提供了此输出,而无需省略stdout打印.

你能解释一下,我在那里失踪了吗?

zde*_*bra 5

虽然这看起来像魔术,但它有一个合乎逻辑的解释.Go不保证goroutines执行的顺序.在给定的代码段代码中有三个goroutine,但实际上有四个:执行开始时产生的第一个.

省略了stdout打印

这个goroutine产生了三个小睡功能并继续其计划.它是如此之快,以至于它wg.Wait()在任何产生的goroutine能够调用之前执行wg.Add(1).结果wg.Wait()没有阻止执行和程序结束.

之前打印到标准输出 wg.Wait()

在这种情况下,程序执行是不同的,goroutines能够进行 wg.Add(1)调用,因为主要的goroutine并不像第一种情况那样快.此行为无法保证,可以在链接的游乐场示例中看到.

它与stdout打印无关

以下代码示例将提供相同的预期输出:

time.Sleep(time.Second)
wg.Wait()
fmt.Println("all done")
Run Code Online (Sandbox Code Playgroud)

与此fmt.Println()产生的影响相同time.Sleep().

惯用法

规则很简单:wg.Add(1)在产生goroutine之前调用.

package main

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

func main() {
    wg := new(sync.WaitGroup)
    nap := func() {
        time.Sleep(2 * time.Second)
        fmt.Println("nap done")
        wg.Done()
    }

    napCount := 3
    wg.Add(napCount)
    for i := 0; i < napCount; i++ {
        go nap()
    }

    wg.Wait()
    fmt.Println("all done")
}
Run Code Online (Sandbox Code Playgroud)

  • 虽然这是一个很好的信息,但我没有看到任何地方的实际答案,即在调度goroutine之前总是调用`wg.Add`. (3认同)
  • 是的,如果他在*启动goroutine之前调用Add*,那就可以修复它. (2认同)