考虑此代码段
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上的相同代码确实提供了此输出,而无需省略标准输出。
你能告诉我,我在那儿想念什么吗?
答案 0 :(得分:5)
即使这看起来像魔术,也有合乎逻辑的解释。 Go不能保证goroutines执行的顺序。在给定的代码段中生成了三个goroutine,但是实际上,它们中有四个:第一个在执行开始时生成。
此goroutine产生了三个小睡功能,并继续其计划。它是如此之快,以至于它在生成的goroutine中的任何一个都能调用wg.Wait()
之前就执行了wg.Add(1)
。结果wg.Wait()
没有阻止执行,程序结束了。
wg.Wait()
之前打印到标准输出在这种情况下,程序执行有所不同,goroutine能够进行wg.Add(1)
调用,因为主goroutine的速度不如第一种情况快。无法保证这种行为,这可以在链接的操场示例中看到。
以下代码示例将提供相同的预期输出:
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")
}