✅ 一、什么是闭包(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 包进行同步控制