`go print(v)`和`go func(){print(v)}()`之间的区别?

Joh*_*now 2 closures pointers go

这是代码:

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()
}
Run Code Online (Sandbox Code Playgroud)

据我所知,函数中的所有goroutine wrong1共享相同的局部变量v.在执行goroutine时,值v可能等于任何值data,因此该函数会打印三次随机数据.

但是,我无法理解为什么函数的fix1行为不同(它只打印data一次每个值).

pet*_*rSO 5


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


去:常见问题(FAQ)

关闭作为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
    }
}
Run Code Online (Sandbox Code Playgroud)

人们可能错误地期望看到a,b,c作为输出.您可能会看到的是c,c,c.这是因为循环的每次迭代都使用变量v的相同实例,因此每个闭包共享该单个变量.当封闭运行,它打印在执行fmt.Println时间v的值,但V可以被修改,因为够程推出.

要在启动时将v的当前值绑定到每个闭包,必须修改内部循环以在每次迭代时创建新变量.一种方法是将变量作为参数传递给闭包:

for _, v := range values {
    go func(u string) {
        fmt.Println(u)
        done <- true
    }(v)
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,v的值作为参数传递给匿名函数.然后可以在函数内部访问该值作为变量u.

更简单的是创建一个新的变量,使用一个看似奇怪的声明样式但在Go中运行正常:

for _, v := range values {
    v := v // create a new 'v'.
    go func() {
        fmt.Println(v)
        done <- true
    }()
}
Run Code Online (Sandbox Code Playgroud)

你的wrong1例子,

for _, v := range data {
    go func() {
        print(v)
    }()
}
Run Code Online (Sandbox Code Playgroud)

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

输出:

three
three
three
Run Code Online (Sandbox Code Playgroud)

你的wrong1例子,创建一个新变量,

for _, v := range data {
    v := v
    go func() {
        print(v)
    }()
}
Run Code Online (Sandbox Code Playgroud)

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

输出:

one
two
three
Run Code Online (Sandbox Code Playgroud)

你的wrong1例子,将变量作为参数传递,

for _, v := range data {
    go func(v *field) {
        print(v)
    }(v)
}
Run Code Online (Sandbox Code Playgroud)

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

输出:

one
two
three
Run Code Online (Sandbox Code Playgroud)

fix1(): go print(v)


Go编程语言规范

呼叫

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

f(a1, a2, … an)
Run Code Online (Sandbox Code Playgroud)

用参数a1,a2,... a调用f.除了一种特殊情况,参数必须是可赋值给F的参数类型的单值表达式,并在调用函数之前进行求值.

去声明

函数值和参数在调用goroutine中照常评估.


你的fix1例子,评估v调用函数之前的值,

for _, v := range data {
    go print(v)
}
Run Code Online (Sandbox Code Playgroud)

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

输出:

one
two
three
Run Code Online (Sandbox Code Playgroud)