必须用 sync.Mutex 时:多 goroutine 同时读写共享变量且无其他同步机制;漏锁、锁内耗时、错误初始化、未 defer 解锁均会导致 data race 或死锁。

什么时候必须用 sync.Mutex?
当多个 goroutine 同时读写同一个变量(比如计数器、状态标志、缓存 map)且没有其他同步机制(如 channel)兜底时,sync.Mutex 是最直接、最可控的选择。它不解决“谁先来”,只确保“一次只让一个进来”。
- 漏锁是最高频错误:比如只对写加锁,但读操作没锁 →
go run -race会立刻报 data race - 别在锁里做耗时事:HTTP 调用、大循环、阻塞 IO 都会让其他 goroutine 卡住,哪怕只是 10ms,高并发下也会雪球式拖慢整体吞吐
- 结构体字段声明
mu sync.Mutex就行,千万别new(sync.Mutex)或赋值给另一个变量 —— Go 1.19+ 会 panic -
defer mu.Unlock()是底线,但注意:如果函数中间有return且上面没defer,锁就永远不释放 → 死锁
sync.RWMutex 真的比 Mutex 快吗?
快,但只在「读远多于写」的场景下成立。它的本质是把读和写解耦:多个 RLock() 可以并行,但只要有一个 Lock() 在等,新来的 RLock() 就得排队。
- 写饥饿风险真实存在:如果写操作频繁(比如每秒几十次),读请求可能长期等不到机会,尤其在高负载时
- 不能“读锁升级为写锁”:持有
RLock()时调Lock()会死锁,必须先RUnlock()再Lock() - 配置缓存、状态映射表这类读取密集型结构,用
RWMutex能提升 2–5 倍吞吐;但如果是写多读少(比如日志聚合器),反而更慢
为什么 sync.WaitGroup 总是 Wait 不住?
根本原因只有一个:Add() 没在 goroutine 启动前调用。WaitGroup 的计数器不是原子“监听”,而是靠你手动配平。
-
wg.Add(1)必须出现在go func() { ... }()之前,否则主线程可能已经执行到wg.Wait(),而计数还是 0 → 立刻返回 - 别在 goroutine 里调
wg.Add():它和Wait()之间没有 happens-before 关系,行为未定义 - 循环变量陷阱:写
for i := 0; i 会全打印 3 —— 改成go func(v int) { fmt.Println(v) }(i)
sync.Once 和 atomic.CompareAndSwap 该选哪个?
sync.Once 是开箱即用的安全初始化方案;atomic.CompareAndSwap 是更底层、更轻量的控制权,但要自己兜底全部逻辑。
立即学习“go语言免费学习笔记(深入)”;
- 加载配置、初始化 DB 连接池、启动后台协程 —— 用
Once.Do(),简洁无错 - 需要细粒度控制状态流转(比如 “初始化中 / 初始化失败 / 已就绪” 三态),或性能极端敏感(微秒级延迟要求),才考虑
atomic手写 -
Once内部 panic 不会传播,但如果你的初始化函数 panic,后续所有Do()调用都会 panic —— 所以务必在Do里recover
-race 跑、用压测看、在日志里埋点验证。










