【问题标题】:Why is there a race condition in this program?为什么这个程序中有竞争条件?
【发布时间】:2013-06-21 15:11:17
【问题描述】:

我在看Golang文档中的typical data races,不太明白为什么这个程序有问题:

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()
}

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

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

显然不是发生了什么,而是为什么?

【问题讨论】:

    标签: go race-condition goroutine


    【解决方案1】:

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

    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()
    }
    

    runnable example

    【讨论】:

      【解决方案2】:

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

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

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

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

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

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

      【讨论】:

        【解决方案3】:

        正如其他人所提到的,您的变量 i 在您创建的 goroutines 中使用,但是一旦您的循环已经完成循环,这些 goroutines 可以在将来执行。此时i的值不是5,你所有的goroutine都会被启动,读取i的值(如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

        【讨论】: