go语言sync包的Map源码

Go语言在sync包中给出了线程安全的map。

Map的结构体比较简单。

type Map struct {
   mu Mutex

   // read contains the portion of the map's contents that are safe for
   // concurrent access (with or without mu held).
   //
   // The read field itself is always safe to load, but must only be stored with
   // mu held.
   //
   // Entries stored in read may be updated concurrently without mu, but updating
   // a previously-expunged entry requires that the entry be copied to the dirty
   // map and unexpunged with mu held.
   read atomic.Value // readOnly

   // dirty contains the portion of the map's contents that require mu to be
   // held. To ensure that the dirty map can be promoted to the read map quickly,
   // it also includes all of the non-expunged entries in the read map.
   //
   // Expunged entries are not stored in the dirty map. An expunged entry in the
   // clean map must be unexpunged and added to the dirty map before a new value
   // can be stored to it.
   //
   // If the dirty map is nil, the next write to the map will initialize it by
   // making a shallow copy of the clean map, omitting stale entries.
   dirty map[interface{}]*entry

   // misses counts the number of loads since the read map was last updated that
   // needed to lock mu to determine whether the key was present.
   //
   // Once enough misses have occurred to cover the cost of copying the dirty
   // map, the dirty map will be promoted to the read map (in the unamended
   // state) and the next store to the map will make a new dirty copy.
   misses int
}

 

Map的结构体很简单,通过Mutex来作为加锁的工具,read作为atomic下的Value存放readOnly结构体,readOnly的结构如下。

 

type readOnly struct {
   m       map[interface{}]*entry
   amended bool // true if the dirty map contains some key not in m.
}

 

readOnly的结构很简单,一个内置map和一个bool类型的amended,其中readOnly的map在读取数据的时候不需要加锁,在读取数据的时候也会相对的优先从这里的map进行寻找数据。而相应的,Map结构中还有内置map类型的dirty,其中在dirty中读取数据需要加锁,在读取数据的时候会优先从readOnly中的map找,如果没找到,在在dirty中找,如果在dirty中找到了,则会将Map中int类型的missing加一,当missing超过dirty的大小的时候,则会将dirty的数据复制到readOnly中的map中完成升级。而readOnly中bool类型的amended则表示dirty中有readOnly中没有的数据。

 

 

而在Map中,存在一个expunged指针用来作为将要被移除的键值对。而entry结构体就很简单,只有一个指向value地址的指针。

var expunged = unsafe.Pointer(new(interface{}))
type entry struct {
   // p points to the interface{} value stored for the entry.
   //
   // If p == nil, the entry has been deleted and m.dirty == nil.
   //
   // If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
   // is missing from m.dirty.
   //
   // Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty
   // != nil, in m.dirty[key].
   //
   // An entry can be deleted by atomic replacement with nil: when m.dirty is
   // next created, it will atomically replace nil with expunged and leave
   // m.dirty[key] unset.
   //
   // An entry's associated value can be updated by atomic replacement, provided
   // p != expunged. If p == expunged, an entry's associated value can be updated
   // only after first setting m.dirty[key] = e so that lookups using the dirty
   // map find the entry.
   p unsafe.Pointer // *interface{}
}

 

在sync中的map中,通过Store()方法存储键值对。

 

func (m *Map) Store(key, value interface{}) {
   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 {
      if e.unexpungeLocked() {
         // The entry was previously expunged, which implies that there is a
         // non-nil dirty map and this entry is not in it.
         m.dirty[key] = e
      }
      e.storeLocked(&value)
   } else if e, ok := m.dirty[key]; ok {
      e.storeLocked(&value)
   } else {
      if !read.amended {
         // We're adding the first new key to the dirty map.
         // Make sure it is allocated and mark the read-only map as incomplete.
         m.dirtyLocked()

当Map刚刚通过new()方法建立的时候,还并没有保存任何数据,这个时候,调用Store()传入键值对准备放入Map中。首先会通过atomic.Value的Load()方法取得readOnly,当然此时的readOnly并没有任何数据,所以接下来在readOnly中找不到相应的键值对,那么将会去dirty去寻找相应的键,对于readOnly并不需要加锁,但是一旦涉及到dirty就需要调用Mutex进行加锁,如果找不到,则会判断readOnly中的amended是否为false,用来确认是不是第一次在dirty中添加新的key,由于readOnly刚刚被创建,所有其必然是默认的false,那么则会调用dirtyLocked()方法初始化dirty这个map。

func (m *Map) dirtyLocked() {
   if m.dirty != nil {
      return
   }

   read, _ := m.read.Load().(readOnly)
   m.dirty = make(map[interface{}]*entry, len(read.m))
   for k, e := range read.m {
      if !e.tryExpungeLocked() {
         m.dirty[k] = e
      }
   }
}

 

在这个方法中,将会创建新的map作为dirty,并且遍历readOnly中的数据将其复制在dirty中。

 

 

 

 

func (e *entry) tryExpungeLocked() (isExpunged bool) {
   p := atomic.LoadPointer(&e.p)
   for p == nil {
      if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
         return true
      }
      p = atomic.LoadPointer(&e.p)
   }
   return p == expunged
}

 

但是在复制之前调用tryExpungeLocked()方法保证该键值对不是已经被删除的键值对。

 

在创建完毕dirty之后,就会创建新的readOnly赋给read,同时amended为true,因为此时dirty将会有第一个键值对,而此时readOnly中并没有数据。

而后在dirty中创建key与value地址的指针的键值对,而后给Mutex解锁。

 

但是如果存放键值对一开始,就在readOnly中找到了相应的键,那么就需要调用tryStore()方法进行更新value。

func (e *entry) tryStore(i *interface{}) bool {
   p := atomic.LoadPointer(&e.p)
   if p == expunged {
      return false
   }
   for {
      if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
         return true
      }
      p = atomic.LoadPointer(&e.p)
      if p == expunged {
         return false
      }
   }
}

 

首先会取得key所对应的entry也就是里面的指针,如果已经指向expunged,那么说明该值已经被删除,那么就需要返回false重新返回false,重新对dirty进行操作。而如果不是,那么直接通过cas将原本value地址的指针指向新的value地址就完成了对于read中键值对的更新。

 

 

那么,如果readOnly中没有找到,在dirty中找到了呢,那么还是需要加锁,加锁之后还有去readOnly中再找一次,防止多线程下dirty晋升为read,导致read数据与之前不一致。如果在第二次对readOnly检查中找到了相应的键值对,那么首先确保并不是被删除逇键值对,如果已经被删除,那么先在dirty中更新key对应的空指针,然后调用storeLocked()方法更新entry中的指针指向新的value地址。

如果在加锁的情况下仍旧没有在readOnly中找到,那么如果在dirty中找到,那么就更新key所对应的entry对应的指针就可以了。

 

以上是Map中键值对的存储,而键值对的读取则通过Load()方法。

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
   read, _ := m.read.Load().(readOnly)
   e, ok := read.m[key]
   if !ok && read.amended {
      m.mu.Lock()
      // Avoid reporting a spurious miss if m.dirty got promoted while we were
      // blocked on m.mu. (If further loads of the same key will not miss, it's
      // not worth copying the dirty map for this key.)
      read, _ = m.read.Load().(readOnly)
      e, ok = read.m[key]
      if !ok && read.amended {
         e, ok = m.dirty[key]
         // Regardless of whether the entry was present, record a miss: this key
         // will take the slow path until the dirty map is promoted to the read
         // map.
         m.missLocked()
      }
      m.mu.Unlock()
   }
   if !ok {
      return nil, false
   }
   return e.load()
}

 

当需要根据给出的key取value的时候,首先去readOnly中去寻找相应的键值对,如果有,直接返回。如果没有找到,则需要判断amended是否为true,如果为false说明dirty没有read,没有的数据,那么也没有必要再去dirty找。如果为true,则证明有需要去dirty的必要。那么加锁,继续检查一次read,避免在这期间dirty晋升为read。而后去dirty找,如果找到了,那么就调用missLocked()给miss加一。

 

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
}

 

如果miss大于dirty的长度,则有必要将此时read的数据晋升到read中,那么重新创建一个readOnly,其中的map就是dirty,amended则默认为true,因为新的read的内容就是此时的dirty,两者没有区别。之后清空dirty,并将miss变为0。

 

在完成之后,返回找到的指针对应的地址下的数据。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值