Mutex的核心作用是确保同一时间仅一个goroutine进入临界区以避免数据竞争;必须用指针传递、Lock后立即defer Unlock、读多写少时可换RWMutex,并用-race检测竞态。

Mutex 的核心作用就是:同一时间只允许一个 goroutine 进入临界区,从而避免对共享变量的并发读写导致数据竞争(data race)。 它不是魔法,而是一把“门锁”——你得自己开门、进门、关门;漏关或开错门,照样出问题。
怎么用 sync.Mutex 才算安全?
关键不在“加锁”,而在“锁对地方、解得及时”。最稳妥的写法是:Lock() 后紧跟 defer Unlock(),哪怕函数有多个 return 路径也不会漏解锁。
- ✅ 正确姿势:在临界区开始前调用
c.mu.Lock(),立刻用defer c.mu.Unlock() - ❌ 错误姿势:
if err != nil { return }写在Unlock()前——错误提前返回,锁永远不释放,后续 goroutine 全卡死 - ⚠️ 危险姿势:在
Lock()后做http.Get()或长时间计算——其他 goroutine 干等,吞掉并发收益
为什么传结构体必须用指针?
因为 sync.Mutex 不支持复制。值传递会拷贝整个结构体,包括里面的 mu 字段,但拷贝出来的 mu 是全新实例,和其他副本毫无关系——等于每个 goroutine 都在锁自己的“空气锁”,完全起不到互斥作用。
type Counter struct {
mu sync.Mutex
value int
}
func main() {
var c Counter
go c.Inc() // ❌ 值传递 → 复制 mu → 锁失效
go c.Inc()
// 结果不可预测,大概率不是 2
cPtr := &Counter{} // ✅ 指针传递 → 共享同一个 mu
go cPtr.Inc()
go cPtr.Inc()
// 才能保证 value 正确递增
}
什么时候该换 sync.RWMutex?
当你确认“读远多于写”,比如配置缓存、状态快照、API 响应计数器等场景。RWMutex 允许多个 goroutine 同时 RLock() 读,只有写操作才独占 Lock() —— 性能提升明显,但代价是逻辑更复杂。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 适合:
Value()被调用上千次,Reset()一天只调一次 - ❌ 禁止:
RLock()后试图调用Lock()(会死锁)——Go 不支持锁升级 - ⚠️ 注意:
RWMutex的写锁会阻塞所有新读请求,但已获得RLock()的 goroutine 可继续执行完
怎么发现没锁好?
别靠猜。Go 自带竞态检测器,运行时加 -race 标志,它会在控制台直接打印出哪两行代码在争抢同一变量:
go run -race main.go
输出类似:Read at 0x00c000010240 by goroutine 7 和 Previous write at 0x00c000010240 by goroutine 6 —— 这就是铁证。开发阶段务必常开 -race,CI 流水线也建议加入。
最容易被忽略的一点:锁对象本身必须是同一个实例。全局变量、结构体字段、或通过指针传入的 mutex 都可以;但局部变量 new 出来的、或者 map 里存的 mutex 指针若未统一管理,就可能锁了“假对象”。










