Go sync.Map

268 阅读3分钟

基本介绍

当我们对 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 指向 全局变量 expungeddirty 中不存在该数据

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
}

image.png