代码如下:
type field struct {
name string
}
func print(p *field) {
fmt.Println(p.name)
}
func fix1() {
data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
for _, v := range data {
go print(v)
}
time.Sleep(time.Millisecond * 200)
}
func wrong1() {
data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
for _, v := range data {
go func() {
print(v)
}()
}
time.Sleep(time.Millisecond * 200)
}
func main() {
wrong1()
}
据我了解,函数wrong1
中的所有goroutine共享相同的局部变量v
。在执行例行程序时,v
的值可以等于data
中的任何值,因此该函数将打印3次随机数据。
但是,我无法理解为什么功能fix1
的行为不同(它在data
中的每个值只打印一次)。
答案 0 :(得分:5)
go func() { print(v) }()
Go: Frequently Asked Questions (FAQ)
What happens with closures running as goroutines?
使用并发闭包时可能会引起一些混乱。 考虑以下程序:
func main() { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
可能会错误地期望看到a,b,c作为输出。你会做什么 大概看到的是c,c,c。这是因为 循环使用变量v的相同实例,因此每个闭包共享 该单个变量。当闭包运行时,它输出v的值 在执行fmt.Println时,但是v可能已被修改 自goroutine启动以来。
要将v的当前值绑定到每个闭包启动时,一个 必须修改内部循环以在每次迭代时创建一个新变量。 一种方法是将变量作为参数传递给闭包:
for _, v := range values { go func(u string) { fmt.Println(u) done <- true }(v) }
在此示例中,v的值作为参数传递给 匿名功能。然后可以在函数内部访问该值 作为变量u。
更容易的是使用声明创建一个新变量 样式可能看起来很奇怪,但在Go中效果很好:
for _, v := range values { v := v // create a new 'v'. go func() { fmt.Println(v) done <- true }() }
您的wrong1
示例,
for _, v := range data {
go func() {
print(v)
}()
}
游乐场:https://play.golang.org/p/0w86nvVMt1g
输出:
three
three
three
您的wrong1
示例,创建一个新变量,
for _, v := range data {
v := v
go func() {
print(v)
}()
}
游乐场:https://play.golang.org/p/z5RCI0ZZU8Z
输出:
one
two
three
您的wrong1
示例,将变量作为参数传递,
for _, v := range data {
go func(v *field) {
print(v)
}(v)
}
游乐场:https://play.golang.org/p/1JVI7XYSqvv
输出:
one
two
three
go print(v)
The Go Programming Language Specification
给出函数类型为F的表达式f,
f(a1, a2, … an)
用参数a1,a2,…an调用f。除了一种特殊情况 参数必须是可分配给的单值表达式 F的参数类型,并且在调用该函数之前先求值。
函数值和参数按常规方式在 调用goroutine。
您的fix1
示例,在调用函数之前评估v
的值,
for _, v := range data {
go print(v)
}
游乐场:https://play.golang.org/p/rN3UNaGi-ge
输出:
one
two
three