range复用临时变量
package main
import "sync"
func main() {
wg := sync.WaitGroup{}
si := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for i := range si {
wg.Add(1)
go func() {
println(i)
wg.Done()
}()
}
wg.Wait()
}
9
9
9
9
9
9
9
9
9
9
程序结果并没有如我们预期一样遍历切片,而是全部打印9,有两点原因导致这个问题:
- for range下的迭代变量i的值是共用的。
- main函数所在的goroutine和后续启动的goroutines存在竞争关系。
使用go run-race来看一下数据竞争情况:
==================
WARNING: DATA RACE
Read at 0x00c42007e010 by goroutine 5:
main.main.func1()
/Users/acton_zhang/go/gobook/src/pro7_2/demo1/main.go:13 +0x3b
Previous write at 0x00c42007e010 by main goroutine:
main.main()
/Users/acton_zhang/go/gobook/src/pro7_2/demo1/main.go:10 +0xe8
Goroutine 5 (running) created at:
main.main()
/Users/acton_zhang/go/gobook/src/pro7_2/demo1/main.go:12 +0x13e
==================
1
2
3
4
5
6
7
8
9
9
Found 1 data race(s)
exit status 66
可以看到Goroutine 5和main goroutine存在数据竞争,更进一步证实了range共享临时变量。range在迭代写的过程中,多个goroutine并发地去读。
正确的写法是使用函数参数做一次数据复制,而不是闭包。示例如下:
package main
import "sync"
func main() {
wg := sync.WaitGroup{}
si := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for i := range si {
wg.Add(1)
go func(a int) {
println(a)
wg.Done()
}(i)
}
wg.Wait()
}
1
0
3
2
6
4
5
7
8
9
可以看到新程序的运行结果符合预期。这个不能说是缺陷,而是Go语言设计者为了性能而选择的一种设计方案,因为大多情况下for循环块里的代码是在同一个goroutine里运行的,为了避免空间的浪费和GC的压力,复用了range迭代临时变量。语言使用者明白这个规约,在for循环下调用并发时要复制迭代变量后再使用,不要直接引用for迭代变量。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)