sync.Waitgroup不会阻止执行

时间:2019-01-11 18:33:36

标签: 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")
}

运行此类代码可提供预期的输出:

nap time
nap done
nap done
nap done
all done

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

// fmt.Println("nap time")
wg.Wait()
fmt.Println("all done")

现在输出变为意外:

all done

预计会是:

nap done
nap done
nap done
all done

playground上的相同代码确实提供了此输出,而无需省略标准输出。

你能告诉我,我在那儿想念什么吗?

1 个答案:

答案 0 :(得分:5)

即使这看起来像魔术,也有合乎逻辑的解释。 Go不能保证goroutines执行的顺序。在给定的代码段中生成了三个goroutine,但是实际上,它们中有四个:第一个在执行开始时生成。

省略了标准输出

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

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

在这种情况下,程序执行有所不同,goroutine能够进行wg.Add(1)调用,因为主goroutine的速度不如第一种情况快。无法保证这种行为,这可以在链接的操场示例中看到。

与stdout打印无关

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

time.Sleep(time.Second)
wg.Wait()
fmt.Println("all done")

fmt.Println()time.Sleep()的影响相同。

惯用方式

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

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")
}