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有三种状态:

  1. p = nil:软删除状态。表示条目已经被删除了,dirty中还存在
  2. p = expunged:硬删除状态。表示条目已经被删除了,且dirty中也不存在了。
  3. 存活状态。

读数据流程

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 —