为了账号安全,请及时绑定邮箱和手机立即绑定

如何将方法用作 goroutine 函数

如何将方法用作 goroutine 函数

Go
牧羊人nacy 2021-12-27 15:10:42
我有这个代码。我希望输出:hello : 1 world : 2但它输出:world : 2world : 2我的代码有问题吗?package mainimport (    "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 回答

?
aluckdog

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


查看完整回答
反对 回复 2021-12-27
?
繁花不似锦

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


查看完整回答
反对 回复 2021-12-27
  • 2 回答
  • 0 关注
  • 136 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信