How to use a method as a goroutine function

huangapple go评论146阅读模式
英文:

How to use a method as a goroutine function

问题

我有这段代码。我期望输出:

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)
}
英文:

I have this code. I expect to output:

hello : 1 
world : 2

but it outputs:

world : 2
world : 2

Is there something wrong with my code?

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)
}

答案1

得分: 16

因为PrintData是一个指针接收器,而task是一个值,所以编译器在进行方法调用时会自动取task的地址。结果调用的效果与(&task).PrintData()相同。

变量task在每次循环迭代中被设置为不同的值。第一个goroutine直到task被设置为第二个值时才会运行。运行这个示例可以看到在每次迭代中传递给PrintData的地址是相同的。

有几种方法可以解决这个问题。第一种方法是在切片中使用*Task

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

playground示例

第二种方法是在循环内部创建一个新变量:

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

playground示例

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

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

playground示例

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

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

playground示例

这个问题类似于闭包和goroutine FAQ中讨论的问题。两个问题之间的区别在于传递指针给goroutine函数的机制。问题中的代码使用了方法的接收器参数。FAQ中的代码使用了闭包

英文:

Because PrintData is a pointer receiver and task is a value, the compiler automatically takes the address of task when making the method call. The resulting call is the same as (&task).PrintData().

The variable task is set to a different value on each iteration through the loop. The first goroutine doesn't run until task is set to second value. Run this example to see that the same address is passed to PrintData on each iteration.

There are a few ways to fix this. The first is to use *Task in the slice:

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

playground example

The second is to create a new variable inside the loop:

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

playground example

A third is to take the address of the slice element (using the automatically inserted address operation):

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

playground example

Yet another option is to change PrintData to a value receiver to prevent the method call from automatically taking the address of task:

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

playground example

This issue is similar to the issue discussed in the closures and goroutines FAQ. The difference between the issues is the mechanism used to pass a pointer to the goroutine function. The code in the question uses the method's receiver argument. The code in the FAQ uses a closure.

答案2

得分: 3

> Go常见问题(FAQ)
>
> 闭包在作为goroutine运行时会发生什么?
>
> 在并发编程中,使用闭包可能会引起一些困惑。
> 考虑以下程序:
>
> func main() {
> done := make(chan bool)
>
> values := []string{"a", "b", "c"}
> for _, v := range values {
> go func() {
> fmt.Println(v)
> done <- true
> }()
> }
>
> // 在退出之前等待所有goroutine完成
> for _ = range values {
> <-done
> }
> }
>
> 你可能错误地期望看到输出为a、b、c。但实际上,你可能会看到c、c、c。这是因为循环的每次迭代都使用相同的变量v实例,因此每个闭包共享该变量。当闭包运行时,它打印的是v在执行fmt.Println时的值,但是自启动goroutine以来,v可能已经被修改。为了在问题发生之前检测到这种情况和其他问题,可以运行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 // 创建一个新的 '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
英文:

> Go Frequently Asked Questions (FAQ)
>
> What happens with closures running as goroutines?
>
> Some confusion may arise when using closures with concurrency.
> Consider the following program:
>
> 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
> }
> }
>
> One might mistakenly expect to see a, b, c as the output. What you'll
> probably see instead is c, c, c. This is because each iteration of the
> loop uses the same instance of the variable v, so each closure shares
> that single variable. When the closure runs, it prints the value of v
> at the time fmt.Println is executed, but v may have been modified
> since the goroutine was launched. To help detect this and other
> problems before they happen, run go vet.
>
> To bind the current value of v to each closure as it is launched, one
> must modify the inner loop to create a new variable each iteration.
> One way is to pass the variable as an argument to the closure:
>
> for _, v := range values {
> go func(u string) {
> fmt.Println(u)
> done <- true
> }(v)
> }
>
> In this example, the value of v is passed as an argument to the
> anonymous function. That value is then accessible inside the function
> as the variable u.
>
> Even easier is just to create a new variable, using a declaration
> style that may seem odd but works fine in Go:
>
> for _, v := range values {
> v := v // create a new 'v'.
> go func() {
> fmt.Println(v)
> done <- true
> }()
> }
>
>
> ----------

Just create a new variable for the closure using a declaration style that may seem odd but works fine in Go. Add task := task. For example,

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

type Task struct {
	name string
	data int32
}

func (this *Task) PrintData() {
	fmt.Println(this.name, &quot;:&quot;, this.data)
}

func main() {
	tasks := []Task{{&quot;hello&quot;, 1}, {&quot;world&quot;, 2}}
	for _, task := range tasks {
		task := task
		go task.PrintData()
	}
	time.Sleep(time.Second * 5000)
}

Output:

hello : 1
world : 2

huangapple
  • 本文由 发表于 2016年3月21日 09:55:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/36121984.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定