插件窝 干货文章 Go语言闭包:为什么循环中创建的匿名函数总是输出相同的值?

Go语言闭包:为什么循环中创建的匿名函数总是输出相同的值?

闭包 变量 函数 循环 811    来源:    2025-03-13

在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执行
}

在这个例子中,我们期望输出 0, 1, 2,但实际上可能会输出 3, 3, 3。这是因为所有的匿名函数都共享同一个变量 i,当这些函数被执行时,i 的值已经变成了 3(循环结束时的值)。

解决方法

要解决这个问题,可以通过将循环变量 i 作为参数传递给匿名函数,这样每个闭包函数都会捕获到不同的 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执行
}

在这个修改后的版本中,每个匿名函数都接收一个独立的 i 值,因此输出会是 0, 1, 2

另一种解决方法

另一种方法是创建一个新的局部变量,并在每次循环中将其赋值给循环变量 i 的当前值:

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执行
}

在这个版本中,i := i 创建了一个新的局部变量 i,每个闭包函数都捕获了这个局部变量,因此输出也会是 0, 1, 2

总结

在循环中使用闭包时,务必注意闭包捕获的是变量的引用,而不是变量的值。为了避免所有闭包共享同一个变量,可以通过将变量作为参数传递给闭包,或者在循环中创建一个新的局部变量来确保每个闭包捕获到不同的值。