SyMind / learning

路漫漫其修远兮,吾将上下而求索。
9 stars 1 forks source link

Go 闭包 #59

Open SyMind opened 1 year ago

SyMind commented 1 year ago

原文:https://stackoverflow.com/questions/25919213/why-does-go-handle-closures-differently-in-goroutines

问题

考虑下面的代码:

package main

import "fmt"
import "time"

func main() {
    for _, s := range []string{"foo", "bar"} {
        x := s
        func() {
            fmt.Printf("s: %s\n", s)
            fmt.Printf("x: %s\n", x)
        }()
    }
    fmt.Println()
    for _, s := range []string{"foo", "bar"} {
        x := s
        go func() {
            fmt.Printf("s: %s\n", s)
            fmt.Printf("x: %s\n", x)
        }()
    }
    time.Sleep(time.Second)
}

这段代码产生以下输出:

s: foo
x: foo
s: bar
x: bar

s: bar
x: foo
s: bar
x: bar

我很好奇为什么?

  1. s 的值在 goroutine 中的解释与在常规 func 调用中的结果不同
  2. 为什么在这两种情况下都将其分配给循环内的局部变量

回答

Go 中的闭包依赖词法作用域。这意味着任何在闭包内对外部变量的引用,都不是一个拷贝。for 循环实际上会在多次循环中重用同一个变量,所以你需要处理 s 变量在读写时竞态条件。

x 会被分配一个新的变量(使用 :=)并拷贝 s,来保证每次的结果都是正确的。

通常,最佳实践是通过参数传入需要的变量,例如:

for _, s := range []string{"foo", "bar"} {
    x := s
    go func(s string) {
        fmt.Printf("s: %s\n", s)
        fmt.Printf("x: %s\n", x)
    }(s)
}