3

我正在查看 Golang 文档中的典型数据竞争,我不太明白为什么这个程序会出现问题:

func main() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
        go func() {
            fmt.Println(i) // Not the 'i' you are looking for.
            wg.Done()
        }()
    }
    wg.Wait()
}

5, 5, 5, 5, 5会在我期望它打印时打印0, 1, 2, 3, 4(不一定按此顺序)。

在我看来,当 goroutine 在循环内创建时,值i是已知的(例如,可以log.Println(i)在循环开始时执行 a 并查看预期值)。所以我希望 goroutine 能够捕获i它创建时的值并在以后使用它。

显然这不是正在发生的事情,但为什么呢?

4

3 回答 3

8

您的函数文字i从外部范围引用。如果您请求 的值i,您将获得i现在的任何值。为了使用iGo 例程创建时的值,请提供一个参数:

func main() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
        go func(i int) {
            fmt.Println(i)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

可运行的例子

于 2013-06-21T15:28:12.490 回答
2

该变量i未在函数字面量中声明,因此它成为闭包的一部分。理解闭包的一个简单方法是考虑如何实现它们。简单的解决方案是使用指针。你可以认为函数字面量被编译器重写为一些

func f123(i *int) {
        fmt.Println(*i)
        wg.Done            
}
  • 在调用此函数时,通过 go 语句,将i变量的地址传递给被调用的 f123(编译器生成的示例名称)。

  • 您可能正在使用默认的 GOMAXPROCS==1,因此 for 循环执行 5 次而没有任何调度,因为循环不执行 I/O 或其他“调度点”,例如通道操作。

  • 当循环终止时i == 5wg.Waitfinally 触发执行五个准备运行的 goroutine(对于 f123)。当然,它们都具有指向同一个整数变量的相同指针i

  • 现在每个 goroutine 都看到相同的i值 5。

当 GOMAXPROCS > 1 运行时,或者当循环产生控制时,您可能会得到不同的输出。这也可以通过例如runtime.Gosched来完成。

于 2013-06-21T15:32:01.760 回答
0

正如其他人所提到的,您的变量i在您创建的 goroutines 中使用,但是一旦您的循环已经完成循环,这些 goroutines 可以在将来执行。此时,iis not的值5,所有的 goroutine 都被启动,读取i(as 5) 的值并继续他们愉快的方式。

我相信 FUZxxl 提到了将值i作为参数传递给函数的用法。我认为这对于相当复杂的系统来说是一个好主意,特别是如果您正在执行 go 例程的函数不是内联闭包。但是,在大多数情况下,我认为为每个 go 例程创建一个新的临时变量会更简洁:

http://play.golang.org/p/6dnkrEGfhn

func main() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
        myi := i
        go func() {
            fmt.Println(myi)
            wg.Done()
        }()
    }
    wg.Wait()
}

效果是一样的,可以说这是一个偏好问题,而且确实如此。这是我的偏好:p

于 2013-06-22T09:58:37.533 回答