Go 专家(03):sync.Map —— 什么时候用,性能特点
更新时间:2026-09-01。本文是
languages/go/expert/专家层第 03 篇,接 sync.Once。Go 标准库提供了sync.Map,一个并发安全的 map。但它的性能特征和普通 map + Mutex 不同,只有在特定的读写模式下才更快。理解它的底层结构,才能用对sync.Map。
本文要回答的问题
sync.Map的底层结构是什么?为什么比 Mutex+map 在某些场景快?- 什么时候用
sync.Map,什么时候用Mutex+map? Load、Store、LoadOrStore、Delete、Range怎么用?sync.Map的 read-only 和 dirty map 怎么切换?
一、基本用法
var m sync.Map
// 存
m.Store("key1", "value1")
m.Store("key2", "value2")
// 取
v, ok := m.Load("key1")
if ok {
fmt.Println(v.(string))
}
// 取或存(key 存在返回旧值,不存在存新值)
v, loaded := m.LoadOrStore("key3", "value3")
if loaded {
fmt.Println("already exists:", v.(string))
}
// 删除
m.Delete("key1")
// 遍历
m.Range(func(key, value any) bool {
fmt.Println(key, value)
return true // 返回 false 终止遍历
})注意: sync.Map 的 key 和 value 都是 any,需要类型断言。
二、底层结构:双 map
type Map struct {
mu Mutex // 保护 dirty map
read atomic.Pointer[readOnly] // 只读 map(无锁读)
dirty map[any]*entry // 可写 map(需要锁)
misses int // 读 miss 次数
}关键设计:
- read:只读 map,无锁读,原子指针更新
- dirty:可写 map,需要加锁
- misses:读 miss 计数,达到阈值触发 read 从 dirty 提升
Load 流程: 读 miss 时,从 dirty 里读,misses 增加,达到阈值后把 dirty 提升为 read。
Store 流程: 如果 key 在 read 里,直接 CAS 更新 entry,无锁;如果 key 不在 read 里,需要加锁操作 dirty。
三、什么时候用 sync.Map
sync.Map 在 read-mostly 场景下比 Mutex + map 快:
- key 写入一次,然后多次读取(读多写少)
- 多个 goroutine 读写不同的 key(减少锁竞争)
- key 不会频繁更新
不适合的场景:
- 频繁写同样的 key(每次都要升级到 dirty)
- 频繁插入新 key(每次都要加锁操作 dirty)
- 少量 key 的简单 map(Mutex 开销更小)
四、sync.Map vs Mutex+map 性能对比
| 场景 | sync.Map | Mutex+map |
|---|---|---|
| 读多写少(key 不更新) | ✅ 快(无锁读) | ❌ 每次读都要拿锁 |
| 写多读少 | ❌ 慢(dirty 升级频繁) | ✅ 快 |
| key 频繁写入新 key | ❌ 每次都要加锁 | ✅ 简单 |
| 少量 key | ❌ 结构复杂,性能优势不明显 | ✅ 简单直接 |
经验法则: 先别用 sync.Map,先用 Mutex+map。如果 pprof 证明锁竞争是瓶颈,再考虑换成 sync.Map。
五、LoadOrStore 的原子性
// 等价于:
// if key exists, return old value
// else store new value, return new value
v, loaded := m.LoadOrStore("key", "value")这个操作是原子的,避免在并发场景下需要先 Load 再 Store 的竞争窗口。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 大量写新 key | 频繁加锁操作 dirty,性能差 | 用 Mutex+map |
| 频繁读不存在的 key | 每次读 miss 都加锁读 dirty | 用 Load 检查 ok 再断言 |
| 忘记类型断言 | 拿到的是 any,直接用 panic | 断言:v.(string) |
用 sync.Map 当普通 map 用 | 比普通 map + Mutex 慢 | 非 read-mostly 场景用 Mutex |
相关与延伸
下一篇:sync.Cond —— 条件变量用法和误区;sync.Map 是 read-mostly 场景的特化优化,和 map 的底层对比见 map 底层。
一句话总结
sync.Map:底层是 read-only 无锁 map + dirty 可写 map,读 miss 到阈值后提升 dirty 为 read;适合 read-mostly 场景(key 写入一次,多次读取),不适合频繁写新 key 的场景;Mutex+map 在大多数场景下更简单,sync.Map 只在 pprof 证明锁竞争是瓶颈时才需要换;LoadOrStore 是原子的,Range 遍历时不要修改 map。