我正在查看Golang文档中的typical data races,我不太明白为什么这个程序有问题:
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i) // Not the 'i' you are looking for.
wg.Done()
}()
}
wg.Wait()
}
当我希望它打印5, 5, 5, 5, 5
时(不一定按此顺序),它会打印0, 1, 2, 3, 4
。
我看到它的方式,当在循环内创建goroutine时,i
的值是已知的(例如,可以在循环开始时执行log.Println(i)
并查看期望值)。所以我希望goroutine在创建时捕获i
的值并稍后再使用它。
显然,这不是发生了什么,而是为什么?
答案 0 :(得分:7)
您的函数文本引用外部作用域中的i
。如果您请求i
的值,则会获得现在i
的值。为了在创建Go例程时使用i
的值,请提供参数:
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}
答案 1 :(得分:2)
变量i
未在函数文字中声明,因此它成为闭包的一部分。如何理解闭包的一个简单方法是考虑如何实现它们。简单的解决方案是使用指针。您可以认为函数文字由编译器重写为某些
func f123(i *int) {
fmt.Println(*i)
wg.Done
}
在调用此函数时,通过go语句,i
变量的地址将传递给被调用的f123(编译器生成的示例名称)。
您可能使用默认的GOMAXPROCS == 1,因此for循环执行5次而没有任何调度,因为循环没有I / O或其他“调度点”,例如通道操作。
当循环终止时,使用i == 5
,wg.Wait
最终触发执行五个,准备运行goroutines(对于f123)。它们当然都具有指向同一整数变量i
的相同指针。
每个goroutine现在都看到相同的i
值为5。
使用GOMAXPROCS&gt;运行时,您可能获得不同的输出; 1,或当循环产生控制时。这也可以通过例如runtime.Gosched来完成。
答案 2 :(得分:0)
正如其他人所提到的,你的变量i
在你创建的goroutine中使用,但是一旦你的循环已经完成循环,那些goroutine将来可以执行。此时,i
的值不是5
,所有的常规例程都会被提升,读取i
的值(作为5
)并继续快乐的方式。
我相信FUZxxl提到使用将值i
作为参数传递给函数。我认为对于相当复杂的系统来说这是一个好主意,特别是如果你正在进行的一个常规函数不是内联闭包。但是,在大多数情况下,我认为为每个例程创建一个新的临时变量要清晰得多:
http://play.golang.org/p/6dnkrEGfhn
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
myi := i
go func() {
fmt.Println(myi)
wg.Done()
}()
}
wg.Wait()
}
效果是一样的,可以说它是一个偏好的问题,而且是。这是我的偏好:p