2 回答
TA贡献1847条经验 获得超7个赞
因为PrintData是一个指针接收者并且task是一个值,所以编译器task在进行方法调用时会自动取地址。结果调用与 相同(&task).PrintData()。
task在循环的每次迭代中,该变量被设置为不同的值。第一个 goroutine 在task设置为第二个值之前不会运行。运行此示例以查看在每次迭代时将相同的地址传递给 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)
}
这个问题类似于闭包和 goroutines FAQ 中讨论的问题。问题之间的区别在于用于将指针传递给 goroutine 函数的机制。问题中的代码使用方法的接收者参数。FAQ 中的代码使用了一个闭包。
TA贡献1851条经验 获得超4个赞
在并发使用闭包时可能会出现一些混淆。考虑以下程序:
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 的相同实例,因此每个闭包共享该单个变量。当闭包运行时,它会打印 fmt.Println 执行时 v 的值,但 v 可能在 goroutine 启动后被修改。为了帮助在这些问题和其他问题发生之前检测它们,请运行 go vet。
要在启动时将 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
- 2 回答
- 0 关注
- 136 浏览
添加回答
举报