“ go print(v)”和“ go func(){print(v)}()`?

时间:2018-12-31 13:06:32

标签: pointers go closures

代码如下:

type field struct {
    name string
}

func print(p *field) {
    fmt.Println(p.name)
}

func fix1() {
    data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
    for _, v := range data {
        go print(v)
    }
    time.Sleep(time.Millisecond * 200)
}

func wrong1() {
    data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
    for _, v := range data {
        go func() {
            print(v)
        }()
    }
    time.Sleep(time.Millisecond * 200)
}

func main() {
    wrong1()
}

据我了解,函数wrong1中的所有goroutine共享相同的局部变量v。在执行例行程序时,v的值可以等于data中的任何值,因此该函数将打印3次随机数据。

但是,我无法理解为什么功能fix1的行为不同(它在data中的每个值只打印一次)。

1 个答案:

答案 0 :(得分:5)


wrong1():go func() { print(v) }()


  

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。这是因为   循环使用变量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
    }()
}

您的wrong1示例,

for _, v := range data {
    go func() {
        print(v)
    }()
}

游乐场:https://play.golang.org/p/0w86nvVMt1g

输出:

three
three
three

您的wrong1示例,创建一个新变量,

for _, v := range data {
    v := v
    go func() {
        print(v)
    }()
}

游乐场:https://play.golang.org/p/z5RCI0ZZU8Z

输出:

one
two
three

您的wrong1示例,将变量作为参数传递,

for _, v := range data {
    go func(v *field) {
        print(v)
    }(v)
}

游乐场:https://play.golang.org/p/1JVI7XYSqvv

输出:

one
two
three

fix1():go print(v)


  

The Go Programming Language Specification

     

Calls

     

给出函数类型为F的表达式f,

f(a1, a2, … an)
     

用参数a1,a2,…an调用f。除了一种特殊情况   参数必须是可分配给的单值表达式   F的参数类型,并且在调用该函数之前先求值。

     

Go statements

     

函数值和参数按常规方式在   调用goroutine。


您的fix1示例,在调用函数之前评估v的值,

for _, v := range data {
    go print(v)
}

游乐场:https://play.golang.org/p/rN3UNaGi-ge

输出:

one
two
three