Go 为什么在这个程序中有一个竞赛条件?

Go 为什么在这个程序中有一个竞赛条件?,go,race-condition,goroutine,Go,Race Condition,Goroutine,我正在看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.Wa

我正在看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()
}
func main(){
var wg sync.WaitGroup
工作组.添加(5)
对于i:=0;i<5;i++{
go func(){
fmt.Println(i)//不是你要找的“i”。
wg.Done()
}()
}
wg.Wait()
}
当我希望它打印
0,1,2,3,4
时,它打印
5,5,5,5
(不一定按此顺序)

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


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

您的函数文本引用了外部范围中的
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()
}
func main(){
var wg sync.WaitGroup
工作组.添加(5)
对于i:=0;i<5;i++{
go func(i int){
fmt.Println(一)
wg.Done()
}(一)
}
wg.Wait()
}

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

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

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

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

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


当运行GOMAXPROCS>1时,或者当循环产生控制时,您可能会得到不同的输出。例如,也可以这样做。

正如其他人提到的,您的变量
i
在您创建的goroutine中使用,但是这些goroutine可以在将来执行,一旦您的循环已经完成循环。此时,
i
的值不是
5
,您所有的围棋程序都会启动,读取
i
(如
5
)的值并继续他们的快乐之路

我相信fuzzxl提到了将值
I
作为参数传递给函数的用法。我认为这对于相当复杂的系统来说是一个好主意,特别是如果你正在启动一个go例程的函数不是内联闭包的话。但是,在大多数情况下,我认为只为每个go例程创建一个新的临时变量要干净得多:

func main(){
var wg sync.WaitGroup
工作组.添加(5)
对于i:=0;i<5;i++{
myi:=i
go func(){
fmt.Println(myi)
wg.Done()
}()
}
wg.Wait()
}
效果是一样的,可以说这是一个偏好的问题,确实如此。这是我的偏好:p

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