sync.rwmutex在写占比超15%~20%、读逻辑隐含写操作、或存在写饥饿时比sync.mutex更慢;其状态切换开销、读锁未释放阻塞写、以及rlock/runlock配对失败均导致性能下降和死锁风险。

sync.RWMutex 什么时候比 sync.Mutex 更慢
读多写少时 sync.RWMutex 确实能提升并发读吞吐,但别一上来就换。当写操作占比超过 15%~20%,或读逻辑里隐含写(比如读完顺手改个 lastAccess 字段),sync.RWMutex 的内部状态切换开销反而可能高于 sync.Mutex。
- 写饥饿:大量读者持续活跃时,新写请求可能永远抢不到锁,尤其写操作本身耗时(如加载配置文件)
- RLock() 和 RUnlock() 必须严格成对;漏一次
RUnlock(),后续所有Lock()就永久阻塞 - 别在
defer里无条件调RUnlock():如果RLock()因context取消失败,就不该解锁
高频 map 并发访问卡死?别锁整个 map
一把 sync.Mutex 锁住整个 map,等于让所有 goroutine 排队过独木桥。真正要做的,是分片——把 key 拆到多个子 map,每个配独立锁。
-
ShardedMap.Get中的哈希取模必须用无符号类型:uint32(hash) % uint32(shardCount),否则负数索引直接 panic - 分片锁不支持跨分片原子操作(如全量
sum),这类需求要么退全局锁,要么改用双缓冲 +atomic.Value - 哈希函数选
fnv32或xxhash.Sum32,避免标准库map自带哈希的随机性导致分布不均
锁里千万别干这些事
锁本身不慢,慢的是你让它干了调度器不该管的事。
- HTTP 请求、数据库查询、JSON 解析、文件 IO —— 全部移出临界区。锁内只做内存读写
- 必须触发异步动作(如发通知)?锁内只
select发信号到channel,由独立 worker 处理 - 定时刷新类写操作(如 token 缓存重载),用双缓冲:新数据加载完成后再
atomic.StorePointer切换指针,避免写过程长期持锁
atomic.Value 不是万能的“无锁对象”
atomic.Value 只保证“整体赋值”原子,内部字段仍需额外同步。它适合存不可变配置、缓存对象等“整块换”的场景。
- 误用
atomic.LoadPointer去读一个未用atomic.StorePointer写入的地址 →race detector报 Data race - 涉及多个字段联动更新?老老实实用锁,别硬套 CAS
- 计数器、开关标志、单次初始化指针优先用
sync/atomic,而不是atomic.Value
runtime.futex 占 CPU 超过 10%,就得动手——别等它飙到 20% 才反应过来。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











