如何将方法用作goroutine函数

时间:2016-03-21 01:55:18

标签: go 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)
}

2 个答案:

答案 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()
}

playground example

第二种是在循环中创建一个新变量:

tasks := []Task{{"hello", 1}, {"world", 2}}
for _, task := range tasks {
    task := task
    go task.PrintData()
}

playground example

第三种方法是获取切片元素的地址(使用自动插入的地址操作):

tasks := []Task{{"hello", 1}, {"world", 2}}
for i := range tasks {
    go tasks[i].PrintData()
}

playground example

另一种选择是将PrintData更改为值接收器,以防止方法调用自动获取task的地址:

func (this Task) PrintData() {
    fmt.Println(this.name, ":", this.data)
}

playground example

此问题与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