Go 闭包

127 阅读2分钟

✅ 一、什么是闭包(Closure)

闭包 = 函数 + 它引用的外部变量的环境

在 Go 中,函数是“一等公民”,可以作为值传递。闭包是指:函数内部引用了其外部作用域的变量,并且这个函数可以在外部作用域结束后依然使用这些变量。

🔹 示例

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

f := adder()
fmt.Println(f(1)) // 1
fmt.Println(f(2)) // 3

这里 sum 是定义在外部作用域的变量,返回的匿名函数引用了它,形成闭包。


✅ 二、Go 闭包的变量捕获规则

🔹 捕获“变量”本身,而不是变量的“值”


for i := 0; i < 3; i++ {
	go func() {
		fmt.Println(i) // 输出可能是 3 3 3,而不是 0 1 2
	}()
}

这里所有 goroutine 共享外部变量 i 的地址,当 goroutine 执行时 i 早就变成 3。

✅ 正确写法


for i := 0; i < 3; i++ {
	go func(n int) {
		fmt.Println(n)
	}(i) // 显式传值,避免共享引用
}

✅ 三、闭包与 defer

defer 延迟函数和闭包组合时也需要注意变量捕获:


for i := 0; i < 3; i++ {
	defer func() {
		fmt.Println(i) // 输出:3 3 3
	}()
}

✅ 正确写法:


for i := 0; i < 3; i++ {
	defer func(n int) {
		fmt.Println(n) // 输出:2 1 0
	}(i)
}

✅ 四、闭包与函数工厂(函数返回函数)

Go 常用闭包构建“函数工厂”或“状态机”:

func makeCounter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2

每个闭包都维持自己的环境,非常适合保存状态。


✅ 五、闭包的生命周期

  • 被捕获的变量的生命周期不会随着外部函数退出而销毁
  • 它会随着闭包函数一起被 GC 管理。

✅ 六、闭包在并发场景的注意事项

  • 闭包中访问共享变量时,要注意数据竞争(race condition)
  • 尽量使用参数传值(避免变量共享)
  • 如果确实需要共享变量,需使用 sync.Mutex、channel 或 atomic 包进行同步控制