Golang中sync.map深入探究
sync.Map 是 Go 语言标准库中提供的并发安全的 map 实现,采用了以空间换取时间的策略。
数据结构
Map
1type Map struct {
2 mu Mutex // 互斥锁
3 read atomic.Value // 只读map
4 dirty map[any]*entry //
5 misses int // 只读map中miss的次数
6}
在Map中的字段意义如下:
- mu:互斥锁
- read:无锁只读的map,对应的类型实际上是readOnly
- dirty:加锁的map
- misses:只读map的miss次数
readOnly
1type readOnly struct {
2 m map[any]*entry // 只读map
3 amended bool // 如果dirty map中有一些key在只读map中时没有的,则为true .
4}
当amended字段为true的时候,则说明在dirty的map中有只读map中不存在的值,这时候在访问的话,除了readonly这个map需要访问,还需要访问dirty这个map。
entry
1type entry struct {
2 // p 指向存储在条目中的 interface{} 值。
3 //
4 // 如果 p == nil,则条目已被删除,并且 m.dirty == nil 或 m.dirty[key] 是 e。
5 //
6 // 如果 p == expunged,则条目已被删除,m.dirty != nil,并且该条目在 m.dirty 中缺失。
7 //
8 // 否则,该条目是有效的,并记录在 m.read.m[key] 中,并且如果 m.dirty != nil,也记录在 m.dirty[key] 中。
9 //
10 // 可以通过原子替换为 nil 来删除条目:当 m.dirty 下一次创建时,它将原子地将 nil 替换为 expunged,并且不设置 m.dirty[key]。
11 //
12 // 条目的关联值可以通过原子替换来更新,前提是 p != expunged。如果 p == expunged,条目的关联值只能在首先设置 m.dirty[key] = e 后更新,以便使用 dirty map 的查找找到该条目。
13 p unsafe.Pointer // *interface{}
14}
在entry中的p有三种状态:
- p = nil:软删除状态。表示条目已经被删除了,dirty中还存在
- p = expunged:硬删除状态。表示条目已经被删除了,且dirty中也不存在了。
- 存活状态。
读数据流程
Map.load()
1func (m *Map) Load(key any) (value any, ok bool) {
2 read, _ := m.read.Load().(readOnly)
3 e, ok := read.m[key]
4
5 // 如果没有找到,并且只读map中的amended字段为true的话,就还需要去dirtymap中找
6 if !ok && read.amended {
7
8 // 加锁
9 m.mu.Lock()
10
11 // double check,再次确认一下
12 read, _ = m.read.Load().(readOnly)
13 e, ok = read.m[key]
14 if !ok && read.amended {
15 e, ok = m.dirty[key]
16 // 更新一下misses
17 m.missLocked()
18 }
19 m.mu.Unlock()
20 }
21 if !ok {
22 return nil, false
23 }
24
25 // 见下面
26 return e.load()
27}
entry.load()
1func (e *entry) load() (value any, ok bool) {
2 p := atomic.LoadPointer(&e.p)
3 if p == nil || p == expunged {
4 // 这两种情况(软删除,硬删除)都是已经删除的情况,所以直接return了nil
5 return nil, false
6 }
7 return *(*any)(p), true
8}
Map.missLocked()
1func (m *Map) missLocked() {
2 // 累加misses次数
3 m.misses++
4 if m.misses < len(m.dirty) {
5 // 如果misses的次数还小于dirtymap的容量,就暂时什么都不做,直接返回
6 return
7 }
8
9 // 如果misses的次数已经超过了dirtymap的容量了,则将dirtymap直接覆盖到只读map去
10 // 此时readOnly中的amended也被初始化为了false
11 m.read.Store(readOnly{m: m.dirty})
12
13 // 清空dirtymap
14 m.dirty = nil
15
16 // 重置misses次数
17 m.misses = 0
18}
只读map没有写的操作,只有上面看到的直接被替换的过程。
写数据流程
Map.Store()
1func (m *Map) Store(key, value any) {
2 read, _ := m.read.Load().(readOnly)
3 // 首先看下是否是软删除的,如果是软删除则直接通过cas修改引用就可以了,
4 // 因为软删除的数据在dirtymap中还是存在的,两个map的数据的引用指向同一个地方
5 if e, ok := read.m[key]; ok && e.tryStore(&value) {
6 return
7 }
8
9 m.mu.Lock()
10 read, _ = m.read.Load().(readOnly)
11 // double check
12 if e, ok := read.m[key]; ok {
13 if e.unexpungeLocked() {
14 m.dirty[key] = e
15 }
16 e.storeLocked(&value)
17 } else if e, ok := m.dirty[key]; ok {
18 e.storeLocked(&value)
19 } else {
20 // 两个map都没有
21 if !read.amended {
22 // 如果原来amended为false的话,将被设置为true
23 m.dirtyLocked()
24 m.read.Store(readOnly{m: read.m, amended: true})
25 }
26 m.dirty[key] = newEntry(value)
27 }
28 m.mu.Unlock()
29}
entry.tryStore()
1// 尝试恢复一个软删除的数据
2func (e *entry) tryStore(i *any) bool {
3 for {
4 p := atomic.LoadPointer(&e.p)
5 if p == expunged {
6 // 如果是硬删除,那就没办法了,直接返回
7 return false
8 }
9 if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
10 // 如果是软删除的,则通过cas恢复只读map中的值的引用
11 return true
12 }
13 }
14}
Map.dirtyLocked()
1func (m *Map) dirtyLocked() {
2 if m.dirty != nil {
3 // 此时dirty和map应该会有一样的数据,应为在上面判断了!read.amended进来的。
4 return
5 }
6
7 read, _ := m.read.Load().(readOnly)
8 m.dirty = make(map[any]*entry, len(read.m))
9
10 // 将非删除态的数据全部同步至dirtymap中。
11 // 在这里软删除态的会被更新成硬删除,并且会被过滤掉
12 for k, e := range read.m {
13 if !e.tryExpungeLocked() {
14 m.dirty[k] = e
15 }
16 }
17}
entry.tryExpungeLocked()
1func (e *entry) tryExpungeLocked() (isExpunged bool) {
2 p := atomic.LoadPointer(&e.p)
3 for p == nil {
4 // 如果是软删除,则更新成硬删除,并返回true
5 if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
6 return true
7 }
8 p = atomic.LoadPointer(&e.p)
9 }
10 return p == expunged
11}
删数据流程
Map.Delete()
1func (m *Map) Delete(key any) {
2 m.LoadAndDelete(key)
3}
4
5func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
6 read, _ := m.read.Load().(readOnly)
7 // 首先还是从只读map中查询
8 e, ok := read.m[key]
9
10 // 如果不存在,且dirtymap中有额外的数据
11 // 则再去dirtymap中查询
12 if !ok && read.amended {
13 m.mu.Lock()
14 read, _ = m.read.Load().(readOnly)
15 e, ok = read.m[key]
16 // double check
17 if !ok && read.amended {
18 e, ok = m.dirty[key]
19 delete(m.dirty, key)
20 m.missLocked()
21 }
22 m.mu.Unlock()
23 }
24
25 // 如果再只读map中存在
26 if ok {
27
28 // 使用cas的方法将当前entry的指针指向nil
29 // 即成为软删除状态
30 return e.delete()
31 }
32 return nil, false
33}
entry.delete()
1func (e *entry) delete() (value any, ok bool) {
2 for {
3 p := atomic.LoadPointer(&e.p)
4 if p == nil || p == expunged {
5 return nil, false
6 }
7 if atomic.CompareAndSwapPointer(&e.p, p, nil) {
8 return *(*any)(p), true
9 }
10 }
11}
遍历流程
Map.Range()
1func (m *Map) Range(f func(key, value any) bool) {
2
3 read, _ := m.read.Load().(readOnly)
4 // 判断一下只读map中的数据是否完整
5 if read.amended {
6 // 不完整,执行以下类似于missLocked方法的操作
7 m.mu.Lock()
8 read, _ = m.read.Load().(readOnly)
9 if read.amended {
10 read = readOnly{m: m.dirty}
11 m.read.Store(read)
12 m.dirty = nil
13 m.misses = 0
14 }
15 m.mu.Unlock()
16 }
17
18 for k, e := range read.m {
19 v, ok := e.load()
20 if !ok {
21 continue
22 }
23 if !f(k, v) {
24 break
25 }
26 }
27}
— END —