我有这个代码。我希望输出:
hello : 1
world : 2
但输出:
world : 2
world : 2
我的代码有问题吗?
package main
import (
"fmt"
"time"
)
type Task struct {
name string
data int32
}
func (this *Task) PrintData() {
fmt.Println(this.name, ":", this.data)
}
func main() {
tasks := []Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
go task.PrintData()
}
time.Sleep(time.Second * 5000)
}
答案 0 :(得分:8)
因为PrintData
是指针接收器而task
是一个值,所以编译方法时编译器会自动获取task
的地址。结果调用与(&task).PrintData()
相同。
变量task
在循环的每次迭代中设置为不同的值。第一个goroutine不会运行,直到task
设置为第二个值。运行this example以查看在每次迭代时将相同的地址传递给PrintData。
有几种方法可以解决这个问题。第一种是在切片中使用*Task
:
tasks := []*Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
go task.PrintData()
}
第二种是在循环中创建一个新变量:
tasks := []Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
task := task
go task.PrintData()
}
第三种方法是获取切片元素的地址(使用自动插入的地址操作):
tasks := []Task{{"hello", 1}, {"world", 2}}
for i := range tasks {
go tasks[i].PrintData()
}
另一种选择是将PrintData更改为值接收器,以防止方法调用自动获取task
的地址:
func (this Task) PrintData() {
fmt.Println(this.name, ":", this.data)
}
此问题与issue discussed in the closures and goroutines FAQ类似。问题之间的区别是用于将指针传递给goroutine函数的机制。问题中的代码使用方法的接收器参数。常见问题解答中的代码使用closure。
答案 1 :(得分:2)
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。这是因为每次迭代都是如此 loop使用变量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 }() }
只需使用声明样式为闭包创建一个新变量,该声明样式可能看似奇怪但在Go中工作正常。添加task := task
。例如,
package main
import (
"fmt"
"time"
)
type Task struct {
name string
data int32
}
func (this *Task) PrintData() {
fmt.Println(this.name, ":", this.data)
}
func main() {
tasks := []Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
task := task
go task.PrintData()
}
time.Sleep(time.Second * 5000)
}
输出:
hello : 1
world : 2