如何将方法用作 goroutine 函数

我有这个代码。我希望输出:


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)

}


牧羊人nacy
浏览 137回答 2
2回答

aluckdog

因为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 中的代码使用了一个闭包。

繁花不似锦

在并发使用闭包时可能会出现一些混淆。考虑以下程序:func main() {&nbsp; &nbsp; done := make(chan bool)&nbsp; &nbsp; values := []string{"a", "b", "c"}&nbsp; &nbsp; for _, v := range values {&nbsp; &nbsp; &nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(v)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; done <- true&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }&nbsp; &nbsp; // wait for all goroutines to complete before exiting&nbsp; &nbsp; for _ = range values {&nbsp; &nbsp; &nbsp; &nbsp; <-done&nbsp; &nbsp; }}人们可能会错误地期望看到 a、b、c 作为输出。你可能会看到的是 c、c、c。这是因为循环的每次迭代都使用变量 v 的相同实例,因此每个闭包共享该单个变量。当闭包运行时,它会打印 fmt.Println 执行时 v 的值,但 v 可能在 goroutine 启动后被修改。为了帮助在这些问题和其他问题发生之前检测它们,请运行 go vet。要在启动时将 v 的当前值绑定到每个闭包,必须修改内部循环以在每次迭代时创建一个新变量。一种方法是将变量作为参数传递给闭包:for _, v := range values {&nbsp; &nbsp; go func(u string) {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(u)&nbsp; &nbsp; &nbsp; &nbsp; done <- true&nbsp; &nbsp; }(v)}在这个例子中,v 的值作为参数传递给匿名函数。然后可以在函数内部访问该值作为变量 u。更简单的方法是创建一个新变量,使用看起来很奇怪但在 Go 中工作正常的声明样式:for _, v := range values {&nbsp; &nbsp; v := v // create a new 'v'.&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(v)&nbsp; &nbsp; &nbsp; &nbsp; done <- true&nbsp; &nbsp; }()}只需使用声明样式为闭包创建一个新变量,这可能看起来很奇怪,但在 Go 中工作正常。添加task := task. 例如,package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "time")type Task struct {&nbsp; &nbsp; name string&nbsp; &nbsp; data int32}func (this *Task) PrintData() {&nbsp; &nbsp; fmt.Println(this.name, ":", this.data)}func main() {&nbsp; &nbsp; tasks := []Task{{"hello", 1}, {"world", 2}}&nbsp; &nbsp; for _, task := range tasks {&nbsp; &nbsp; &nbsp; &nbsp; task := task&nbsp; &nbsp; &nbsp; &nbsp; go task.PrintData()&nbsp; &nbsp; }&nbsp; &nbsp; time.Sleep(time.Second * 5000)}输出:hello : 1world : 2
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go