特别提醒,本文所涉及的源码是go1.21.0 darwin/amd64
文件位置:runtime/map.go
普通的map
并不是并发安全的一个数据结构,因此想要使用并发安全的map
,sync
包下提供了一个sync.map
数据结构
1 数据结构 1 2 3 4 5 6 7 type Map struct { mu Mutex read atomic.Pointer[readOnly] dirty map [any]*entry misses int }
read:无锁化的map
,实际类型为atomic.Pointer
dirty:加锁的map
,是一个普通的map
misses:每次从read
中读取失败,misses
都会加1,达到一定阈值后,会将dirty
提升为read
1 2 3 4 type readOnly struct { m map [any]*entry amended bool }
m:read
中的map
,存放k-v
对
amended:标识read
和dirty
中的元素是否一致
1 2 3 type entry struct { p atomic.Pointer[any] }
p:k-v
对中的value
,使用atomic.Pointer
的形式,通过entry.p
的指针来获取值
2 entry的几种状态 2.1 p==nil(软删除) 此时p
是nil
,m.dirty
等于nil
或者m.dirty[key]
指向该entry
2.2 p==expunged(硬删除) 1 2 var expunged = new (any)
2.3 p指向正常值的地址 这种情况就是k-v
对没删除的情况,可以通过m.read.m[key]
找到,如果此时dirty
也不为nil
,同样也可以通过m.dirty[key]
找到,两者实际指向的是同一个值。
3 Load 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func (m *Map) Load(key any) (value any, ok bool ) { read := m.loadReadOnly() e, ok := read.m[key] if !ok && read.amended { m.mu.Lock() read = m.loadReadOnly() e, ok = read.m[key] if !ok && read.amended { e, ok = m.dirty[key] m.missLocked() } m.mu.Unlock() } if !ok { return nil , false } return e.load() }
首先会调用loadReadOnly
函数获取read
1 2 3 4 5 6 7 func (m *Map) loadReadOnly() readOnly { if p := m.read.Load(); p != nil { return *p } return readOnly{} }
如果未初始化,则在该函数中初始化,否则直接返回m.read
通过key
来读取read.m
中的value
,然后存在该元素则直接返回值就行,否则要上锁,再判断m
中是否存在该key
,如果还是不存在则去dirty
中查找。
1 2 3 4 5 6 7 8 9 10 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 }
如果在read
和dirty
都没找到,则返回nil
,如果找到了则调用load
返回对应的值
1 2 3 4 5 6 7 8 9 func (e *entry) load() (value any, ok bool ) { p := e.p.Load() if p == nil || p == expunged { return nil , false } return *p, true }
4 Store 1 2 3 func (m *Map) Store(key, value any) { _, _ = m.Swap(key, value) }
Store
函数是通过调用Swap
函数来实现逻辑功能的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 func (m *Map) Swap(key, value any) (previous any, loaded bool ) { read := m.loadReadOnly() if e, ok := read.m[key]; ok { 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 { if v := e.swapLocked(&value); v != nil { loaded = true previous = *v } } else { if !read.amended { m.dirtyLocked() m.read.Store(&readOnly{m: read.m, amended: true }) } m.dirty[key] = newEntry(value) } m.mu.Unlock() return previous, loaded }
1 2 3 4 5 6 7 8 9 10 11 12 13 func (e *entry) trySwap(i *any) (*any, bool ) { for { p := e.p.Load() if p == expunged { return nil , false } if e.p.CompareAndSwap(p, i) { return p, true } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (m *Map) dirtyLocked() { if m.dirty != nil { return } read := m.loadReadOnly() m.dirty = make (map [any]*entry, len (read.m)) for k, e := range read.m { if !e.tryExpungeLocked() { m.dirty[k] = e } } }
5 Delete 1 2 3 func (m *Map) Delete(key any) { m.LoadAndDelete(key) }
map
的Delete
操作都是调用LoadAndDelete
来实现的,因此直接看它是怎么实现的即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func (m *Map) LoadAndDelete(key any) (value any, loaded bool ) { read := m.loadReadOnly() e, ok := read.m[key] if !ok && read.amended { m.mu.Lock() read = m.loadReadOnly() e, ok = read.m[key] if !ok && read.amended { e, ok = m.dirty[key] delete (m.dirty, key) m.missLocked() } m.mu.Unlock() } if ok { return e.delete () } return nil , false }
1 2 3 4 5 6 7 8 9 10 11 12 13 func (e *entry) delete () (value any, ok bool ) { for { p := e.p.Load() if p == nil || p == expunged { return nil , false } if e.p.CompareAndSwap(p, nil ) { return *p, true } } }
为什么这里删除p
的时候不是直接将其置为expunged
而是置为nil
呢?
因为在tryExpungeLocked
函数中,会将nil
设置为expunged
1 2 3 4 5 6 7 8 9 10 11 func (e *entry) tryExpungeLocked() (isExpunged bool ) { p := e.p.Load() for p == nil { if e.p.CompareAndSwap(nil , expunged) { return true } p = e.p.Load() } return p == expunged }
注意到如果 key 同时存在于read
和 dirty
中时,删除只是做了一个标记,将p
置为 nil
;而如果仅在 dirty
中含有这个 key
时,会直接删除这个key
。原因在于,若两者都存在这个 key
,仅做标记删除,可以在下次查找这个key
时,命中 read
,提升效率。若只有在dirty
中存在时,read
起不到“缓存”的作用,直接删除。
6 Range 由用户实现,Range
将遍历map
中的所有k-v
对,将他们传给f
函数,如果f
返回false
则停止遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func (m *Map) Range(f func (key, value any) bool ) { read := m.loadReadOnly() if read.amended { m.mu.Lock() read = m.loadReadOnly() if read.amended { 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 } } }
7 总结 什么时候会出现expunge
?
当key
的值在 read
中为 nil
,随后由其他操作触发read
向 dirty
复制,nil
被转变为expunge
。此时 dirty
不为空,且key
在dirty
中不存在。