在Go语言中,闭包(closure)是指一个函数值(function value)捕获了其外部作用域中的变量。当你在循环中使用匿名函数(闭包)时,如果这个闭包捕获了循环变量,那么所有闭包实例都会共享同一个循环变量。由于闭包是延迟执行的(例如在循环结束后才执行),它们会捕获循环变量的最终值,而不是每次迭代时的值。
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i)
}()
}
time.Sleep(time.Second) // 等待goroutine执行完毕
}
3
3
3
共享变量:在上面的代码中,匿名函数捕获了循环变量 i
。由于 i
是循环变量,它在每次迭代时都会被更新。所有的匿名函数都共享同一个 i
变量。
延迟执行:go
关键字启动了新的 goroutine,这些 goroutine 是并发执行的。由于 goroutine 的调度是异步的,循环可能会在 goroutine 开始执行之前就已经完成。因此,当 goroutine 开始执行时,i
的值已经是循环结束后的最终值(在这个例子中是 3
)。
为了避免这个问题,你可以在每次迭代时创建一个新的局部变量,并将循环变量的值赋给这个局部变量。这样每个闭包都会捕获不同的变量实例。
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 3; i++ {
i := i // 创建一个新的局部变量
go func() {
fmt.Println(i)
}()
}
time.Sleep(time.Second) // 等待goroutine执行完毕
}
0
1
2
i := i
创建了一个新的局部变量 i
,它的作用域仅限于当前迭代。每个 goroutine 捕获的是不同的 i
变量,因此输出的值是正确的。你也可以将循环变量作为参数传递给匿名函数:
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 3; i++ {
go func(i int) {
fmt.Println(i)
}(i)
}
time.Sleep(time.Second) // 等待goroutine执行完毕
}
0
1
2
i
作为参数传递给匿名函数,每个 goroutine 都会接收到一个独立的 i
值,因此输出的值是正确的。在Go语言中,闭包捕获的是变量的引用,而不是变量的值。因此,在循环中使用闭包时,需要注意循环变量的作用域和生命周期。通过创建局部变量或将循环变量作为参数传递给闭包,可以避免闭包捕获到循环变量的最终值。