因为for循环变量i被复用,所有goroutine共享同一内存地址,循环结束时i定格为3,闭包读取的均为该最终值;正确做法是传值(go func(val int){}(i))或在闭包内声明局部变量(v := i)。

闭包捕获变量时为什么总是输出 3?
因为 for 循环中的变量 i 是同一个地址,所有 goroutine 都引用它。循环结束时 i == 3,所以每个闭包打印的都是最终值。
这不是 Go 特有,而是所有按引用捕获变量的语言共性(如 JS、C#、Python 的 lambda)。关键不是“闭包不安全”,而是你没控制好捕获方式。
- 错误写法:
go func() { fmt.Println(i) }()—— 捕获的是外部i的地址 - 正确写法:
go func(val int) { fmt.Println(val) }(i)—— 立即传值,每个 goroutine 拥有独立副本 - 等价写法:
go func() { val := i; fmt.Println(val) }()—— 在 goroutine 内部创建局部变量
值类型 vs 引用类型闭包的线程行为差异
值类型(int、string、struct)捕获时会复制,修改闭包内变量不影响原值;引用类型(*T、slice、map、chan)捕获的是指针或头信息,多个闭包操作同一底层数组/哈希表就立刻引发竞争。
常见陷阱:
立即学习“go语言免费学习笔记(深入)”;
-
slice虽是引用类型,但其 header(指针、长度、容量)是值传递;若多个闭包往同一个底层数组追加元素,append可能导致数据覆盖或 panic -
map直接并发读写会 panic:fatal error: concurrent map writes - 闭包里调用
mu.Lock()但忘记defer mu.Unlock(),会导致死锁或后续 goroutine 永久阻塞
sync.Mutex 和 sync.RWMutex 怎么选?
别一上来就加 sync.Mutex。先看访问模式:如果读远多于写(比如配置缓存、状态快照),sync.RWMutex 允许多个 goroutine 同时读,性能明显更好。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
但要注意:
-
RUnlock()必须配对RLock(),漏掉一个就会卡住所有后续写操作 -
RWMutex不支持“读升级为写”——不能在持有读锁时再尝试获取写锁,会死锁 - 写操作频繁(>10%)时,
RWMutex的额外开销可能反超Mutex,实测更慢
简单判断:单个字段读写 → Mutex;只读为主的大结构体 → RWMutex;高频更新的计数器 → atomic.Int64 更轻量。
闭包 + 锁组合时容易漏掉的细节
闭包里用锁,最容易忽略的是锁作用域和生命周期。比如把 sync.Mutex 声明在函数内、又在闭包中使用,看似没问题,但若该闭包被多次调用或跨 goroutine 复用,锁实例可能被复制(值类型),导致锁失效。
必须确保:
- 锁变量是地址传递(指针或 struct 字段),不能是闭包参数传入的副本
-
defer mu.Unlock()要在闭包内部,且紧贴mu.Lock()后面,否则 panic 时可能跳过解锁 - 不要在闭包里直接捕获整个 struct 指针并调用其带锁方法——确认该方法本身是否已加锁,避免重复加锁或漏锁
真正难的从来不是“加不加锁”,而是“锁什么、锁多久、谁负责释放”。闭包让变量生命周期变得隐式,稍不注意,锁就形同虚设。

















