基本介绍
当我们对 Go 语言中 Map 这个数据结构进行并发操作的时候,程序会显式的抛出 fatal error: concurrent map writes 这样一个错误来终止程序的运行,避免并发读写可能导致的数据竞争和不一致性问题
但是如果每一次对 Map 的操作都加锁的话导致性能的下降,为此 Go 标准库提供了 sync.Map,它是一个并发安全的 map,不需要用户手动添加或释放锁,可以有效避免资源竞争等并发问题,并且在某些场景下有比直接使用锁更高的性能。
源码分析
数据结构
type Map struct {
mu Mutex // 互斥锁
read atomic.Pointer[readOnly] // 一个只读的 map, 支持无锁操作
dirty map[any]*entry // 可读可写的 map,需要先获取锁
misses int // 计数器, readOnly未命中次数
}
type readOnly struct {
m map[any]*entry // readOnly map
amended bool // 标记 readOnly 相比于 dirty 是否存在数据缺失
}
type entry struct {
p atomic.Pointer[any]
}
通过 atomic.Pointer 存储 kv 对中的 value,entry.P 有三种状态用来标识 key-entry 状态
- 正常存活,
entry.P指向 value - 软删除,
entry.P指向 nil,逻辑删除,用户无法查到但是dirty中仍然存在该数据 - 硬删除,
entry.P指向 全局变量expunged,dirty中不存在该数据
Load(key)
func (m *Map) Load(key any) (value any, ok bool) {
// 尝试从 ReadOnly 中获取 value
read := m.loadReadOnly()
e, ok := read.m[key]
// 如果 value 不存在, 或者 ReadOnly 数据缺失
if !ok && read.amended {
m.mu.Lock()
// 二次检查
read = m.loadReadOnly()
e, ok = read.m[key]
if !ok && read.amended {
e, ok = m.dirty[key]
// misses++
m.missLocked()
}
m.mu.Unlock()
}
// 不存在
if !ok {
return nil, false
}
// 存在, 返回数据
return e.load()
}
func (e *entry) load() (value any, ok bool) {
p := e.p.Load()
// 软删除
if p == nil || p == expunged {
return nil, false
}
return *p, true
}
Store(key, value)
func (m *Map) Store(key, value any) {
_, _ = m.Swap(key, value)
}
func (m *Map) Swap(key, value any) (previous any, loaded bool) {
read := m.loadReadOnly()
if e, ok := read.m[key]; ok {
// 尝试在 ReadOnly 中更新 value
if v, ok := e.trySwap(&value); ok {
if v == nil {
return nil, false
}
return *v, true
}
}
m.mu.Lock()
read = m.loadReadOnly()
if e, ok := read.m[key]; ok {
// 如果是硬删除状态
if e.unexpungeLocked() {
m.dirty[key] = e
}
if v := e.swapLocked(&value); v != nil {
loaded = true
previous = *v
}
} else if e, ok := m.dirty[key]; ok {
// ReadOnly 不存在,但是 dirty 中存在
if v := e.swapLocked(&value); v != nil {
loaded = true
previous = *v
}
} else {
// 均不存在,插入数据
if !read.amended {
// 如果 dirty 是空的,将 ReadOnly 数据拷贝到 dirty, 对应 missLocked()
// 同时剔除软删除和硬删除状态的数据
m.dirtyLocked()
m.read.Store(&readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
return previous, loaded
}
Delete(key)
func (m *Map) Delete(key any) {
m.LoadAndDelete(key)
}
func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
read := m.loadReadOnly()
e, ok := read.m[key]
// ReadOnly 中不存在, 且 ReadOnly 数据缺失
if !ok && read.amended {
// 加锁并二次校验
m.mu.Lock()
read = m.loadReadOnly()
e, ok = read.m[key]
if !ok && read.amended {
e, ok = m.dirty[key]
// 删除 dirty 中数据
delete(m.dirty, key)
// misses++
m.missLocked()
}
m.mu.Unlock()
}
// ReadOnly 中存在,软删除
if ok {
return e.delete()
}
return nil, false
}
Range()
func (m *Map) Range(f func(key, value any) bool) {
read := m.loadReadOnly()
// ReadOnly 数据缺失
if read.amended {
m.mu.Lock()
read = m.loadReadOnly()
if read.amended {
// 将 dirty 复制到 ReadOnly,等同于 missLocked()
read = readOnly{m: m.dirty}
m.read.Store(&read)
m.dirty = nil
m.misses = 0
}
m.mu.Unlock()
}
// 遍历
for k, e := range read.m {
v, ok := e.load()
if !ok {
continue
}
// 熔断器
if !f(k, v) {
break
}
}
}
总结
entry 状态
// expunged is an arbitrary pointer that marks entries which have been deleted
// from the dirty map.
var expunged = new(any)
ReadOnly -> dirty
dirty -> ReadOnly
func (m *Map) missLocked() {
m.misses++
if m.misses < len(m.dirty) {
return
}
m.read.Store(&readOnly{m: m.dirty})
m.dirty = nil
m.misses = 0
}