为什么sync.Map可以做到线程安全
【摘要】 1 简介为什么 sync.Map 是线程安全的?sync.Map 是 Go 标准库中专为并发访问设计的映射结构。它在内部通过以下机制实现线程安全: 2 sync.Map是安全的线程同步sync.Map 被认为是线程同步安全的,因为它提供了一种用于安全高效的 并发读写的内置机制。它结合使用 原子作、锁和数据结构来确保 其内容保持一致 且可访问 ,即使有多个 goroutine 正在同时修改m...
1 简介
为什么 sync.Map 是线程安全的?
sync.Map 是 Go 标准库中专为并发访问设计的映射结构。它在内部通过以下机制实现线程安全:
2 sync.Map是安全的线程同步
sync.Map 被认为是线程同步安全的,因为它提供了一种用于安全高效的 并发读写的内置机制。
它结合使用 原子作、锁和数据结构来确保 其内容保持一致 且可访问 ,即使有多个 goroutine 正在同时修改map。
这使得 它适合在高并发环境中使用, 其中多个 goroutine 同时访问和修改共享数据。
✅ 1. 内部读写分离结构(dual-map 技术)
sync.Map 内部使用了两个 map:
read map(只读):用于高效并发读取;
dirty map(写 map):当发生写操作或 miss 时使用,并通过标志 misses 控制是否同步合并到 read map。
✅ 2. CAS + 互斥锁混合机制
多数 读操作 是 lock-free(无锁)的,极大提高了性能;
当遇到 key miss 或写操作时,才使用 互斥锁(sync.Mutex) 保证写入安全;
通过“懒同步”机制延迟写入合并,降低锁竞争。
✅ 3. 避免 GC 扫描
sync.Map 避免频繁修改底层结构,因此减少了 map resizing 和 GC 扫描压力。
3 map + sync.Mutex 与 sync.Map 有何不同
特性 sync.Map map + sync.Mutex
并发读写安全 ✅ 内建支持 ✅ 需手动加锁
写性能 高并发下较优秀(写少读多场景) 写多时锁粒度较粗,性能较差
读性能 无锁读性能极高(共享 read map) 每次读都需加锁
内存占用 略高,存在 read/dirty map 和 miss 计数器 更低,结构更简单
使用复杂度 简单,直接调用 Load, Store, Delete, Range 等方法 需手动封装锁逻辑
适用场景 多读少写、高并发读;缓存场景 写多读少、自定义控制并发行为
3 示例
🔹 1. 使用 sync.Map
var m sync.Map
// 写入
m.Store("a", 1)
// 读取
value, ok := m.Load("a")
// 删除
m.Delete("a")
// 遍历
m.Range(func(k, v any) bool {
fmt.Println(k, v)
return true
})
🔸 2. 使用 map + sync.RWMutex
type SafeMap struct {
mu sync.RWMutex
m map[string]int
}
func (s *SafeMap) Set(k string, v int) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[k] = v
}
func (s *SafeMap) Get(k string) (int, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
val, ok := s.m[k]
return val, ok
}
func (s *SafeMap) Delete(k string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.m, k)
}
4 小结建议
场景 推荐使用方式
多读少写(如缓存、配置、黑名单) ✅ 使用 sync.Map
写多读少,或对锁粒度有精细控制要求 ✅ 使用 map + sync.RWMutex
需要迭代时稳定快照 ✅ 手动加锁的普通 map
读写均匀但性能非常关键 ,考虑使用第三方高性能并发 map(如分片 map)sync.Map是 Go 开发人员武器库中的一个强大工具,但它不是灵丹妙药。它的内部结构针对某些用例进行了优化,特别是当读取作较多而写入相对较少时。
在频繁写入或竞争 goroutine 数量较少的情况下,与常规map配合可以更有效。
【版权声明】本文为华为云社区用户翻译文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,
举报邮箱:cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)