Architecture

sync.Map.drawio.svg
图 1:sync.Map 主要结构关系图

sync.Map 是一种并发安全的 map 结构,其中核心字段及其关系如图 1 所示。

sync.Map 使用读写分离的思想结合内置 map 加锁实现了并发安全,其中 read 字段负责常用数据的存储,dirty 字段负责非常用数据的存储,misses 用来判断是否将 dirty 内存储的数据刷新到 read,具体结构如下所示。

  1. type Map struct {
  2. mu Mutex
  3. read atomic.Value // readOnly
  4. dirty map[any]*entry
  5. misses int
  6. }

sync.Map 通过使用 atomic.Value 原子类型来保证了常用数据存储操作的并发安全,因其与本章内容关系不大,因此在这里不做详细展开,只需了解 read 的类型实际上是 sync.readOnly 即可。

  1. type readOnly struct {
  2. m map[any]*entry
  3. amended bool
  4. }

amended 字段是用来判断数据是否是只存储在 dirty 中的 flag,同时可以注意到最终存储到 map 中的都是 entry 结构,其 p 字段存储了最终数据的指针,p 有 nil、expunged 和 otherwise 三种状态,其中 expunged 用来作为 entry 已经被 dirty 删除的标记。

  1. var expunged = unsafe.Pointer(new(any))
  2. type entry struct {
  3. p unsafe.Pointer
  4. }

Method

Load

sync.Map 提供了如 Load、Store、Delete、Range 等许多基础方法,让 sync.Map 的使用变得像普通 map 一样简单,下面是 Load 方法的执行逻辑。

  1. func (m *Map) Load(key any) (value any, ok bool) {
  2. read, _ := m.read.Load().(readOnly)
  3. e, ok := read.m[key]
  4. if !ok && read.amended {
  5. m.mu.Lock()
  6. read, _ = m.read.Load().(readOnly)
  7. e, ok = read.m[key]
  8. if !ok && read.amended {
  9. e, ok = m.dirty[key]
  10. m.missLocked()
  11. }
  12. m.mu.Unlock()
  13. }
  14. if !ok {
  15. return nil, false
  16. }
  17. return e.load()
  18. }

sync.Map 加载数据的情况共分为三种,L14 ~ L16 是 read 和 dirty 都未命中的情况;L17 是直接在 read 中命中数据的情况;L4 ~ L13 是指在 read 中读取不到但是在 dirty 中能够读取到数据的情况,这时会直接在 dirty 中读取数据,这里需要注意的是 L10 的函数,这个函数的作用是刷新 misses 字段的值,同时如果满足一定条件会将 dirty 中的数据刷新到 read 中。

  1. func (m *Map) missLocked() {
  2. m.misses++
  3. if m.misses < len(m.dirty) {
  4. return
  5. }
  6. m.read.Store(readOnly{m: m.dirty})
  7. m.dirty = nil
  8. m.misses = 0
  9. }

Store

L3 ~ L5 是指在 read 中命中并且 entry 未被删除的情况直接存储在 read 中;L8 ~ L12 是将 read 中命中的 entry 存储到 dirty 中并将数据存储到 entry;L13 ~ L14 是在 dirty 命中的 entry 中存储数据;L15 ~ L22 是将 dirty 初始化并将数据存储到 dirty 中。

  1. func (m *Map) Store(key, value any) {
  2. read, _ := m.read.Load().(readOnly)
  3. if e, ok := read.m[key]; ok && e.tryStore(&value) {
  4. return
  5. }
  6. m.mu.Lock()
  7. read, _ = m.read.Load().(readOnly)
  8. if e, ok := read.m[key]; ok {
  9. if e.unexpungeLocked() {
  10. m.dirty[key] = e
  11. }
  12. e.storeLocked(&value)
  13. } else if e, ok := m.dirty[key]; ok {
  14. e.storeLocked(&value)
  15. } else {
  16. if !read.amended {
  17. m.dirtyLocked()
  18. m.read.Store(readOnly{m: read.m, amended: true})
  19. }
  20. m.dirty[key] = newEntry(value)
  21. }
  22. m.mu.Unlock()
  23. }

Delete

Delete 方法调用了 LoadAndDelete 方法,L8 ~ L18 是在数据只存在 dirty 中的情况下根据 key 值将entry 删除,同时尝试将最新数据刷新到 read;L19 ~ L21 是指在 read 中命中 entry,然后将 entry 中数据直接删除。

  1. func (m *Map) Delete(key any) {
  2. m.LoadAndDelete(key)
  3. }
  4. func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
  5. read, _ := m.read.Load().(readOnly)
  6. e, ok := read.m[key]
  7. if !ok && read.amended {
  8. m.mu.Lock()
  9. read, _ = m.read.Load().(readOnly)
  10. e, ok = read.m[key]
  11. if !ok && read.amended {
  12. e, ok = m.dirty[key]
  13. delete(m.dirty, key)
  14. m.missLocked()
  15. }
  16. m.mu.Unlock()
  17. }
  18. if ok {
  19. return e.delete()
  20. }
  21. return nil, false
  22. }

Range

Range 方法允许使用者提供处理数据的函数并通过一个 bool 类型来控制 sync.Map 遍历的中断,L3 ~ L13 是指在数据只存在于 dirty 的情况下将数据刷新到 read 中;L5 ~ L23 是真正执行 Range 函数使用者自定义数据处理函数的位置。

  1. func (m *Map) Range(f func(key, value any) bool) {
  2. read, _ := m.read.Load().(readOnly)
  3. if read.amended {
  4. m.mu.Lock()
  5. read, _ = m.read.Load().(readOnly)
  6. if read.amended {
  7. read = readOnly{m: m.dirty}
  8. m.read.Store(read)
  9. m.dirty = nil
  10. m.misses = 0
  11. }
  12. m.mu.Unlock()
  13. }
  14. for k, e := range read.m {
  15. v, ok := e.load()
  16. if !ok {
  17. continue
  18. }
  19. if !f(k, v) {
  20. break
  21. }
  22. }
  23. }