Go sync.Map 源码

95 阅读3分钟

Go 内置的map类型不是并发安全的,在并发场景中,会引发panic,而在Go编码中会经常在并发中使用map结构,所以Go 创建了sync.Map.

sync.Map主要使用场景是读多写少;下面使用m表示sync.Map

  1. sync.Map 源码中包含的内容

    var expunged = unsafe.Pointer(new(any)) //key 删除标志

    entry: sync.Map 值类型

    type readOnly struct { m map[any]*entry amended bool // 是否Map.dirty 一致 }

    Map: 是一种并发安全的 map[any]*entry{}

  2. entry 源码

    entry表示map中存储的值

    entry 结构体:

   type entry struct {
        /*
           p 指向 interface{} 类型的值,用于保存 entry

           如果p == nil,则 entry 已删除,但还存在于m中,且 m.dirty == nil

           如果p == expunged,则entry 已被删除,m中已无该条目

           否则,entry 仍然有效,且被记录在 m.read.m[key] ,但如果 m.dirty != nil,则在 m.dirty[key] 中

           一个entry可以通过原子替换为nil来删除:当m.dirty下一次创建时,它将原子替换为deleted,并保留m.dirty[key]不设置。

           与一个 entry 关联的值可以被原子替换式的更新,提供的 p != expunged。如果 p == expunged,则与 entry 关联的值只能在 m.dirty[key] = e 设置后被更新,因此会使用 dirty map 来查找 entry。
       */
      p unsafe.Pointer // *interface{}
   }

entry 方法:

    //存储entry
    func (e *entry) storeLocked(i *any) {
       atomic.StorePointer(&e.p, unsafe.Pointer(i))
    }
    
    //获取值
    func (e *entry) load() (value any, ok bool) {
       p := atomic.LoadPointer(&e.p)
       if p == nil || p == expunged {
          return nil, false
       }
       return *(*any)(p), true
    }
    //删除值
    func (e *entry) delete() (value any, ok bool) {
       for {
          p := atomic.LoadPointer(&e.p)
          if p == nil || p == expunged {
             return nil, false
          }
          if atomic.CompareAndSwapPointer(&e.p, p, nil) {
             return *(*any)(p), true
          }
       }
    }
    
    其它方法:
        newEntry(i any) *entry

	func(e *entry) tryStore(i *any) bool		//尝试存储
	func(e *entry)	unexpungeLocked() (wasExpunged bool)	//设置删除
	func(e *entry)	storeLocker(i *any)	//存储值
	func(e *entry)	tryLoadOrStore(i any) (acual any,loaded, ok bool)	//尝试获取或存储
	func(e *entry)	tryExpungeLocked()(isExpungeLocked bool)	//尝试设置expunged

entry包含一个指向interface{}的指针,表示m中key对应的值,通过 entry== nil|expunged 来处理被删除的entry,待GC时将entry进行回收.

3.Map 源码

type Map struct {
   mu Mutex

   read atomic.Value // readOnly 主要通过m.read实现"读多写少"支持,默认读取时不加锁

   dirty map[any]*entry //修改的数据,当m.misses满足条件时,把m.dirty同步给m.read
   
   misses int//计数器,表示m.dirty中有多少与read不同步的次数
}


//读取

func (m *Map) Load(key any) (value any, ok bool) {

    //先从m.read获取key的值
   read, _ := m.read.Load().(readOnly)
   e, ok := read.m[key]
   if !ok && read.amended {//当m.read中不存在key且m.dirty有m.read中不存在的key时
      m.mu.Lock()
    
      read, _ = m.read.Load().(readOnly)
      e, ok = read.m[key]
      if !ok && read.amended {
         e, ok = m.dirty[key]//从m.dirty中读取
        
         m.missLocked()//如果满足同步条件,将m.dirty同步到m.read
      }
      m.mu.Unlock()
   }
   if !ok {
      return nil, false
   }
   return e.load()
}



//存储

func (m *Map) Store(key, value any) {

    //如果m.read中存在key,则更新value
   read, _ := m.read.Load().(readOnly)
   if e, ok := read.m[key]; ok && e.tryStore(&value) {
      return
   }

   m.mu.Lock()
   read, _ = m.read.Load().(readOnly)
   if e, ok := read.m[key]; ok {//若m.read中存在key,则更新
      if e.unexpungeLocked() {//判断entry没有被标记expunged
         m.dirty[key] = e
      }
      e.storeLocked(&value)
   } else if e, ok := m.dirty[key]; ok {//若m.dirty中存在key,则更新
      e.storeLocked(&value)
   } else {//否则,将新建entry
      if !read.amended {//若read于dirty数据一致,则在read中key
         m.dirtyLocked()
         m.read.Store(readOnly{m: read.m, amended: true})
      }
      m.dirty[key] = newEntry(value)
   }
   m.mu.Unlock()
}

其它方法:

    func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool) //读取并更新key,当key存在时,返回原值,不存在是新建key
    func (m *Map) LoadAndDelete(key any) (value any, loaded bool) //读取并删除key
    func (m *Map) Delete(key any) //删除key
    func (m *Map) Range(f func(key, value any) bool) //遍历m
    func (m *Map) missLocked() //将m.dirty同步到m.read,并重置m.dirty
    func (m *Map) dirtyLocked() //将m.read同步到m.dirty

使用m.read.amended 来标识read和dirty数据是否一样

使用m.misses 来决定什么时候进行数据同步.

我使用的Go版本是1.19