rwmutex仅在读占比≥70%时显著更快,90%读场景吞吐高2–5倍;读≤40%时mutex更稳更快,且无写饥饿、死锁风险;40%–60%区间性能接近但rwmutex风险陡增。

选 sync.Mutex 还是 sync.RWMutex,不看文档,只看临界区读写比例和锁等待表现——读远多于写时,RWMutex 才真有优势;否则 Mutex 更稳、更轻、更不容易出错。
sync.RWMutex 的读并发不是无代价的
很多人以为“多个 RLock() 同时存在 = 性能翻倍”,其实不然。RWMutex 内部要维护读者计数、写者状态、等待队列,且当有写锁在排队时,新来的读锁会被阻塞(避免写饥饿),这反而会让本该并发的读操作串行化。
- 读多写少但写操作较频繁(比如每 100 次读就有 1 次写),
RWMutex可能比Mutex更慢 -
RLock()和Lock()不是完全正交的:写锁优先级高于读锁,且写锁会等所有已有读锁释放才上锁 - 没有“读锁升级为写锁”的 API;强行先
RUnlock()再Lock(),中间存在竞态窗口,可能读到过期数据或触发重复写
sync.Mutex 的 Lock/Unlock 必须严格配对
Mutex 看似简单,但最常踩的坑是 Unlock 失败或重复 Unlock。Go 运行时不校验锁持有者,错误调用会导致 panic 或死锁。
- 忘记
Unlock():goroutine 永久阻塞,后续所有Lock()都卡住 - 重复
Unlock():直接 panic:sync: unlock of unlocked mutex - 跨 goroutine 解锁:不允许,
Unlock()必须由同一线程(goroutine)执行 - 推荐写法:
mu.Lock(); defer mu.Unlock(),确保函数退出前必释放
map 并发读写必须加锁,但别盲目套 RWMutex
Go 的 map 本身非并发安全,任何读(m[key])或写(m[key] = val)都需同步。但加什么锁,得看访问模式:
- 如果结构体里只有 map,且读操作占比 >95%,用
RWMutex+RLock()读 /Lock()写,收益明显 - 如果读写交织紧密(如先查再删再插),或写后立刻要读验证,用
Mutex更简单——避免读写锁切换带来的逻辑复杂度 - 注意:
range遍历 map 也是读操作,必须包在RLock()或Lock()内 - 别在
RLock()下做可能导致阻塞的操作(如 channel send / http call),否则会拖慢所有其他读请求
性能差异往往被高估,正确性永远优先
在真实服务中,锁竞争瓶颈通常不在锁原语本身,而在临界区代码是否过长、是否包含 IO 或计算密集操作。一个耗时 10ms 的 Lock() 区域,换 RWMutex 也救不回来。
- 压测前先用
go tool trace或pprof确认瓶颈真在锁争用,而不是业务逻辑或 GC -
RWMutex的内存占用略高(多维护 reader 计数器等字段),零值虽可用,但嵌入结构体时要注意字段对齐影响 - 所有锁都不可复制:若结构体含
sync.Mutex或sync.RWMutex,绝不能用copy()或 struct literal 赋值,否则运行时报fatal error: copy of locked mutex
真正难的不是选哪个锁,而是判断“这里到底要不要锁”——比如用 sync.Map 替代加锁 map,或把共享状态拆成 per-goroutine 缓存+定期 flush,这些设计权衡比锁类型选择影响更大。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











