golang中for range经常会被用来遍历slice、map、chan、array,但是由于在某些情况下,其内部实现并不是你想的那样,所以使用时还是需要特别注意。
以下是两个错误使用for range的场景
package main
func main() {
vals := []int{0, 1, 2}
valptr := make([]*int, 0)
for k, v := range vals {
//v := v
valptr = append(valptr, &v)
}
for _, v := range valptr {
println(v)
println(*v)
}
}
0xc000016140
6
0xc000016140
6
0xc000016140
6
The iteration variables in
for
statements are reused in each iteration. This means that each closure (aka function literal) created in yourfor
loop will reference the same variable (and they'll get that variable's value at the time those goroutines start executing).
go runtime中for range循环只会为v分配一次内存,后续只是给v
赋值;跟for的语义是一样一样的,如下这样理解起来就容易多了。
package main
func main() {
for i := 0; i < 3; i++ {
println("&i=", &i, " i=", i)
}
}
package main
func main() {
kvs := map[string]int{
"zero": 0,
"one": 1,
"two": 2,
}
for _, v := range kvs {
//v := v
defer func() {
println("defer func(): ", v)
}()
}
}
# go run main.go
go func(): 2
go func(): 2
go func(): 2
# go vet
# github.com/username/gomodule
./main.go:19:27: loop variable v captured by func literal
closure(func literal)中捕获变量是通过引用的方式,因此也会像场景1中那样,v
的地址虽然没变,但是值会随着循环变化。
同时,在场景2中,go vet会有对应的提示:
./main.go:19:27: loop variable v captured by func literal
在出现以上提示时,需要特别小心。同时这里要说下:
go vet
是一个很有价值的工具,可以帮助我们发现很多代码问题
但是场景1是没有的提示, 也不能完全依赖go vet来发现错误。
除了通过增加v := v
的方式,还可以通过以下方式:
package main
func main() {
kvs := map[string]int{
"zero": 0,
"one": 1,
"two": 2,
}
for _, v := range kvs {
defer func(v int) {
println("defer func(): ", v)
}(v)
}
}
其实原理是一样的,
v (v1) := v (v2)
括号是注释是显式的创建了一个v的副本,也叫v; 这里两个v的生命周期不同, v2的生命周期是整个for循环,v1的生命周期是for循环中的一个循环,但是这里由于closure对于v1的引用,所以在一个循环结束后,v会发生逃逸,并不会被立即回收;大部分刚入门golang的开发者都会犯类似错误,讲道理这个可以算是语言的缺陷了,毕竟让用户少犯错也是语言的义务。因此社区中会很多proposal想要解决这个问题。
proposal: spec: redefine range loop variables in each iteration
已经有人提了类似Proposal, 有可能Go 2.0
中会彻底解决这个问题。
如下:
for k, v := range vals {
// ...
}
should be equivalent to
for k, v := range vals {
k := k
v := v
// ...
}
https://www.ardanlabs.com/blog/2013/09/iterating-over-slices-in-go.html
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。