为什么这个项目有竞争条件?

时间:2013-06-21 15:11:17

标签: go race-condition goroutine

我正在查看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()
}

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

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

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

3 个答案:

答案 0 :(得分:7)

您的函数文本引用外部作用域中的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

答案 1 :(得分:2)

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

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

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

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

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

使用GOMAXPROCS&gt;运行时,您可能获得不同的输出; 1,或当循环产生控制时。这也可以通过例如runtime.Gosched来完成。

答案 2 :(得分:0)

正如其他人所提到的,你的变量i在你创建的goroutine中使用,但是一旦你的循环已经完成循环,那些goroutine将来可以执行。此时,i的值不是5,所有的常规例程都会被提升,读取i的值(作为5)并继续快乐的方式。

我相信FUZxxl提到使用将值i作为参数传递给函数。我认为对于相当复杂的系统来说这是一个好主意,特别是如果你正在进行的一个常规函数不是内联闭包。但是,在大多数情况下,我认为为每个例程创建一个新的临时变量要清晰得多:

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