什么是闭包

2025-07-09

引言:需求场景分析

老板要求创建一个函数,每次调用使变量减一。

常见错误方案

// 全局变量方案(存在并发安全问题)
var sum = 100

func main() {
    sum--
    fmt.Println(sum)
}
// 文件存储方案(效率低下)
package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    // 从文件读取当前值
    data, _ := os.ReadFile("counter.txt")
    sum, _ := strconv.Atoi(string(data))
    
    // 执行减一操作
    sum--
    
    // 保存新值
    fmt.Println(sum)
    os.WriteFile("counter.txt", []byte(strconv.Itoa(sum)), 0644)
}

闭包解决方案

package main

import "fmt"

func createDecrementer(start int) func() int {
    current := start
    return func() int {
        current--
        return current
    }
}

func main() {
    dec := createDecrementer(100)
    
    fmt.Println(dec()) // 99
    fmt.Println(dec()) // 98
    fmt.Println(dec()) // 97
}

闭包的核心特性

1. 捕获外部变量

func example() func() int {
    externalVar := 100 // 被闭包捕获的变量
    
    return func() int {
        externalVar-- // 操作的是外部作用域的变量
        return externalVar
    }
}

2. 变量生命周期延长

func main() {
    closure := func() {
        local := 100
        return func() {
            local++ // 即使外部函数执行完毕,local仍然存在
            fmt.Println(local)
        }
    }() // 立即执行函数返回闭包
    
    closure() // 101
    closure() // 102
    closure() // 103
}

3. 状态保持

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

func main() {
    c := counter()
    fmt.Println(c()) // 1
    fmt.Println(c()) // 2 (记住前一次状态)
    fmt.Println(c()) // 3 (记住前一次状态)
}

4. 隔离的数据环境

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

func main() {
    c1 := counter() // 创建计数器1
    c2 := counter() // 创建计数器2
    
    fmt.Println(c1()) // 1
    fmt.Println(c1()) // 2
    
    fmt.Println(c2()) // 1 (独立计数)
    fmt.Println(c2()) // 2
}

闭包实际应用场景

1. 状态计数器

func createCounter(start, step int) func() int {
    current := start
    return func() int {
        current += step
        return current
    }
}

func main() {
    // 创建计数器:从10开始,每次加2
    counter := createCounter(10, 2)
    
    fmt.Println(counter()) // 12
    fmt.Println(counter()) // 14
    fmt.Println(counter()) // 16
}

2. 访问控制实现

func createAccessController(initialCredits int) (func(), func() int) {
    credits := initialCredits
    
    // 消耗信用的函数
    use := func() {
        if credits > 0 {
            credits--
        }
    }
    
    // 查看剩余信用的函数
    check := func() int {
        return credits
    }
    
    return use, check
}

func main() {
    useCredit, checkCredit := createAccessController(3)
    
    useCredit()
    fmt.Println("Credits left:", checkCredit()) // 2
    
    useCredit()
    useCredit()
    fmt.Println("Credits left:", checkCredit()) // 0
    
    useCredit() // 无效果,信用已用完
}

3. 缓存机制

func createCache() func(string) (string, bool) {
    cache := make(map[string]string)
    
    return func(key string) (value string, exists bool) {
        if v, ok := cache[key]; ok {
            return v, true
        }
        
        // 模拟数据库查询等耗时操作
        // 这里简化,实际应用中会从数据库或其他来源获取数据
        value = key + "_value"
        cache[key] = value
        return value, false
    }
}

func main() {
    getCached := createCache()
    
    // 第一次访问,未命中缓存
    val, cached := getCached("key1")
    fmt.Println(val, "from cache?", cached) // key1_value from cache? false
    
    // 第二次访问,命中缓存
    val, cached = getCached("key1")
    fmt.Println(val, "from cache?", cached) // key1_value from cache? true
}

闭包与并发的注意事项

package main

import (
    "fmt"
    "sync"
)

func main() {
    counter := func() func() int {
        count := 0
        return func() int {
            count++
            return count
        }
    }()
    
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println(counter())
        }()
    }
    wg.Wait()
    
    // 输出结果可能为:2 1 3 5 4 等不按顺序的数字
    // 这是因为多个goroutine同时访问闭包变量,存在竞态条件
}

安全解决方案

package main

import (
    "fmt"
    "sync"
)

func createSafeCounter() func() int {
    var count int
    var mu sync.Mutex
    
    return func() int {
        mu.Lock()
        defer mu.Unlock()
        count++
        return count
    }
}

func main() {
    counter := createSafeCounter()
    
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println(counter())
        }()
    }
    wg.Wait()
}

闭包内存模型解析

闭包背后的实现原理

Go语言中的闭包本质上是:

  1. 一个持有外部环境变量的结构体

  2. 一个指向函数的指针

当编译器检测到闭包创建时:

  • 会将闭包引用的变量从栈上分配到堆上

  • 创建闭包结构,包含函数指针和捕获的变量

  • 该结构会随着闭包本身的生命周期而存在

闭包陷阱与最佳实践

​闭包陷阱示例:循环变量捕获​

func main() {
    var funcs []func()
    
    for i := 0; i < 3; i++ {
        funcs = append(funcs, func() {
            fmt.Println(i)
        })
    }
    
    for _, f := range funcs {
        f() // 输出:3 3 3,而不是期望的0,1,2
    }
}

​解决方案1:通过参数传递副本​

for i := 0; i < 3; i++ {
    func(i int) {
        funcs = append(funcs, func() {
            fmt.Println(i)
        })
    }(i) // 创建副本
}

​解决方案2:创建新的循环变量​

for i := 0; i < 3; i++ {
    j := i // 创建新的变量
    funcs = append(funcs, func() {
        fmt.Println(j)
    })
}

​闭包最佳实践:​

  1. ​控制闭包大小​​:避免捕获过大的变量

  2. ​明确内存管理​​:对于不再使用的闭包,将其设为nil便于GC回收

  3. ​避免长生命周期闭包​​:必要时使用终结器或手动释放

  4. ​并发安全设计​​:在并发环境下使用锁保护共享变量

  5. ​性能考量​​:简单计数器考虑用原子操作代替

NEXT
golang的反射与断言