sync.Mutex不能防止函数重入,仅保障临界区互斥执行;它不控制函数调用次数,只确保同一时刻至多一个goroutine进入加锁代码段,错误地拆分读写锁、defer位置不当、锁内panic或耗时操作均会导致竞态或性能问题。

sync.Mutex 不能防止函数重入,只能防止临界区并发执行
很多人误以为给一个函数加 sync.Mutex 就能“防重入”,其实不是。互斥锁只管「谁可以进临界区」,不管「谁调用了这个函数」。如果两个 goroutine 同时调用同一个函数,且该函数内部有 mu.Lock(),那它们会排队进入临界区——但这不等于函数本身被“去重”或“串行化调用”。真正的问题常出在:函数逻辑本应幂等或单次生效,但因临界区设计不当,导致多次执行写操作。
典型错误:读-写分离导致 double-check 失效
常见于缓存填充、单例初始化、键存在性检查后写入等场景。例如先 rwlock.RLock() 判断 key 是否存在,再 mu.Lock() 写入——这中间存在竞态窗口,两个 goroutine 可能同时通过读检查,然后都进入写临界区。
- 错误模式:
RLock()→ 检查 →Unlock()→Lock()→ 写入 →Unlock() - 正确做法:所有依赖状态判断 + 修改的操作,必须包裹在同一个
mu.Lock()/mu.Unlock()临界区内 - 不要拆成两段锁;
RWMutex的读锁对写操作无保护力,它只避免读-读阻塞
defer mu.Unlock() 放错位置导致锁泄露
这是高并发下最隐蔽的死锁源头之一。一旦函数提前 return(比如在 if err != nil { return } 之后),而 defer mu.Unlock() 写在函数末尾而非 mu.Lock() 紧后,锁就永远不释放。
- 必须把
defer mu.Unlock()紧跟在mu.Lock()后面,哪怕中间有 error check - 禁止在锁内调用可能 panic 的函数(如
json.Marshal、未判空的指针解引用),否则defer不触发 - 零值
sync.Mutex可直接用,但别把它作为参数传给其他函数——传的是副本,原锁不受影响
锁粒度太大引发吞吐瓶颈
在临界区内做网络请求、文件 I/O 或长循环,会让其他 goroutine 长时间等待,看似“线程安全”,实则性能崩坏。这不是 bug,是设计缺陷。
立即学习“go语言免费学习笔记(深入)”;
- 把耗时操作移出临界区:先计算好要写的数据,再加锁更新共享状态
- 考虑用
sync.Map替代带锁的map,尤其读多写少场景 - 若状态更新逻辑复杂,可改用 channel + 单独 goroutine 串行处理,避免锁竞争
真正难的不是加锁,而是界定“什么才算一次完整的状态变更”。临界区边界一旦画错,再多的 Lock/Unlock 都救不回来。


















