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

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

闭包 变量 捕获 一个 457    来源:    2025-03-13

在Go语言中,闭包(closure)是指一个函数值(function value)捕获了其外部作用域中的变量。闭包在Go中非常常见,尤其是在使用匿名函数时。然而,闭包在使用时可能会遇到一个常见的陷阱:匿名函数中的变量值总是相同的。这个问题的根源在于闭包捕获的是变量的引用,而不是变量的值。

问题描述

考虑以下代码:

package main

import (
    "fmt"
    "time"
)

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

你可能期望输出是:

0
1
2

但实际上,输出可能是:

3
3
3

问题分析

在这个例子中,i 是一个在 for 循环中声明的变量。每次循环迭代时,i 的值都会递增。然而,匿名函数捕获的是 i 的引用,而不是 i 的当前值。因此,当匿名函数执行时,它们都会打印出 i 的最终值(即 3),而不是在创建闭包时的值。

解决方案

要解决这个问题,可以通过在每次循环迭代时创建一个新的局部变量来捕获 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)
}

在这个修改后的代码中,i := i 创建了一个新的局部变量 i,它的作用域仅限于当前循环迭代。因此,每个闭包都会捕获一个不同的 i,输出将是:

0
1
2

另一种解决方案

你也可以将 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)
}

在这个版本中,i 作为参数传递给匿名函数,因此每个闭包都会捕获一个不同的 i 值。输出同样会是:

0
1
2

总结

Go语言中的闭包捕获的是变量的引用,而不是变量的值。因此,在使用闭包时,特别是在循环中,需要注意变量的作用域和生命周期。通过创建新的局部变量或将变量作为参数传递给闭包,可以避免闭包陷阱,确保闭包捕获的是预期的值。