插件窝 干货文章 Go语言闭包陷阱:为什么匿名函数中的变量值总是相同的?

Go语言闭包陷阱:为什么匿名函数中的变量值总是相同的?

函数 变量 匿名 闭包 423    来源:    2025-03-14

在Go语言中,闭包(closure)是一个函数值,它引用了其函数体之外的变量。闭包允许你访问和操作这些外部变量,即使这些变量在其原始作用域之外。然而,使用闭包时可能会遇到一个常见的陷阱,特别是在循环中使用匿名函数时,可能会导致所有匿名函数都引用同一个变量,从而导致意外的行为。

问题描述

假设你有以下代码:

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 0; i < 5; i++ {
        go func() {
            fmt.Println(i)
        }()
    }
    time.Sleep(time.Second)
}

你可能期望输出是 0, 1, 2, 3, 4,但实际上输出可能是 5, 5, 5, 5, 5 或其他重复的值。

原因分析

这个问题的根本原因是所有匿名函数都共享同一个变量 i。在Go语言中,i 是在 for 循环的上下文中声明的,因此所有匿名函数都引用了同一个 i。当 for 循环结束时,i 的值是 5,此时所有的匿名函数才开始执行,因此它们都会打印出 5

解决方案

要解决这个问题,你需要确保每个匿名函数都捕获到 i 的一个副本,而不是共享同一个 i。你可以通过将 i 作为参数传递给匿名函数来实现这一点。

方法1:使用函数参数

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 0; i < 5; i++ {
        go func(i int) {
            fmt.Println(i)
        }(i)
    }
    time.Sleep(time.Second)
}

在这个例子中,i 作为参数传递给匿名函数,因此每个匿名函数都有自己的 i 副本,输出将是 0, 1, 2, 3, 4

方法2:在循环内部声明变量

另一种方法是在循环内部声明一个新的变量,并将其赋值给 i 的当前值:

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 0; i < 5; i++ {
        i := i // 创建一个新的变量
        go func() {
            fmt.Println(i)
        }()
    }
    time.Sleep(time.Second)
}

在这个例子中,i := i 创建了一个新的变量 i,它只在当前循环迭代中有效。因此,每个匿名函数都捕获到不同的 i 值,输出将是 0, 1, 2, 3, 4

总结

在Go语言中使用闭包时,特别是在循环中使用匿名函数时,需要注意变量的作用域和生命周期。为了避免闭包陷阱,可以通过将变量作为参数传递给匿名函数,或者在循环内部声明新的变量来确保每个匿名函数都捕获到正确的变量值。