【发布时间】:2021-11-30 16:05:59
【问题描述】:
和你们大多数人一样,我很熟悉 Go 在 for 循环中重用迭代器变量的事实,因此每个 goroutine 的闭包都会捕获相同的变量。下面的代码就是一个典型的例子,它总是会从每个 goroutine 中打印出循环的最终值:
func main() {
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i) // prints 5 each time
}()
}
time.Sleep(100 * time.Millisecond)
}
我无法找到解释的是,为什么 goroutine 直到循环完成后才会执行。甚至下面的代码也会将ii 的值生成为10,这是在调用goroutine 之后设置的:
func main() {
for i := 0; i < 5; i++ {
ii := i
go func() {
fmt.Println(ii) // prints 10 each time...!
}()
ii = 10
}
time.Sleep(100 * time.Millisecond)
}
与我读过的解释最接近的是 for 循环通常比 goroutine 执行得更快。这给我提出了两个问题: 1. 这是真的吗? 2. 为什么?
【问题讨论】:
-
"将 ii 的值生成为 10,这是在调用 goroutine 之后设置的" goroutines 没有被“调用”。最好说
go f()“创建”一个新的goroutine,一旦goroutine真正开始运行,它将执行f。 -
“我读过的最接近解释的是 for 循环通常比 goroutine 执行得更快。这给我提出了两个问题:1. 这是真的吗?”是的!不!比较 goroutine 和 goroutine 执行的 for 循环的速度是没有意义的。每个代码都由一个 goroutine 执行:你的 for 循环由执行 main 的 goroutine 执行。是的,这也是一个 goroutine(它是在程序启动期间生成的,而不是手动使用
go)。 -
会发生什么:主 goroutine 正在愉快地处理您的 for 循环。这个循环做了一些变量赋值并产生了一些 goroutines。只是偶然(绝对不能保证 goroutines 是如何调度的)主 goroutine 到达 time.Sleep 在任何新的 goroutines 开始执行它们的 Printf 工作之前。如果没有同步(通道、互斥体、等待组等),任何 goroutine 执行顺序都可能发生。
-
并且:推理具有竞争条件的程序是错误的。
-
@Volker 感谢您耐心地纠正我的(许多)误解。您提到“偶然” goroutine 到达 time.Sleep 在 goroutine 执行 Println 语句之前。我理解为什么这应该只是一个机会,但是非常可靠地运行此代码会产生相同的结果。我不应该期望看到更多的结果变化吗?例如。 play.golang.org/p/PA4veXPOd6W