插件窝 干货文章 Go语言闭包中变量作用域为何总是获取循环结束后的值?

Go语言闭包中变量作用域为何总是获取循环结束后的值?

闭包 变量 循环 捕获 32    来源:    2025-03-11

在Go语言中,闭包(closure)是一个函数值,它引用了其函数体之外的变量。闭包函数可以访问并修改这些外部变量的值。然而,当闭包在循环中被创建时,如果不小心处理,可能会导致意外的行为,特别是当闭包捕获循环变量时。

问题描述

在循环中使用闭包时,闭包捕获的变量通常是循环变量的引用,而不是循环变量在每次迭代时的值。这意味着,当闭包最终被执行时,它们会看到循环变量的最终值,而不是在闭包创建时的值。

示例代码

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 在每次迭代时的值。当 goroutine 开始执行时,循环可能已经结束,i 的值已经变成了 3,所以所有 goroutine 都会打印 3

解决方案

要解决这个问题,可以通过将循环变量的值传递给闭包,而不是直接捕获循环变量。这样每个闭包都会捕获到不同的变量副本,而不是共享同一个变量。

方法1:使用函数参数

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

方法2:在循环内部创建新的变量

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

总结

在Go语言中,闭包捕获的是变量的引用,而不是变量的值。因此,在循环中使用闭包时,需要注意变量的作用域问题。通过将循环变量的值传递给闭包,或者在循环内部创建新的变量,可以避免闭包捕获到循环结束后的值。