Go Sync.Map源码阅读

sync.Map

Go语言中内置的map不是并发安全的

sync.Map 有以下特性:

  • 无须初始化,直接声明即可

  • sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。

  • 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。

底层原理

type Map struct {
    mu Mutex

  // 后面是readOnly结构体,依靠map实现,仅仅只用来读
    read atomic.Value // readOnly

    // 这个map主要用来写的,部分时候也承担读的能力
    dirty map[interface{}]*entry

    // 记录自从上次更新了read之后,从read读取key失败的次数,来触发dirty更新到read操作
    misses int
}

type readOnly struct {
    m       map[interface{}]*entry
    // dirty是否包含m中不存在的key
    amended bool // true if the dirty map contains some key not in m.
}

type entry struct {
    p unsafe.Pointer // *interface{}  执行存储值得地址
}

// 后面是readOnly结构体,依靠map实现,仅仅只用来读
read atomic.Value // readOnly

// 这个map主要用来写的,部分时候也承担读的能力
dirty map[interface{}]*entry

// 记录自从上次更新了read之后,从read读取key失败的次数,来触发dirty更新到read操作
misses int
}

type readOnly struct {
m map[interface{}]*entry
// dirty是否包含m中不存在的key
amended bool // true if the dirty map contains some key not in m.
}

type entry struct {
p unsafe.Pointer // *interface{} 执行存储值得地址
}</pre>

使用了两个map,一个叫read,一个叫dirty,两个map存储的都是指针,指向value数据本身,所以两个map是共享value数据的,更新value对两个map同时可见。

read map 使用 atomic.Value,保证读写操作的原子性

dirty map 使用 mutex来保护,进行新增一个key进行存储

结构示意图

通过上面的结构体,我们可以简单画出来一个结构示意图

image.png

Store操作

func (m *Map) Store(key, value interface{}) {
   read, _ := m.read.Load().(readOnly)
   if e, ok := read.m[key]; ok && e.tryStore(&value) {
      return
   }
    
    // 走到这里有两种情况,
    // 1. key不存在 
    // 2. key对应的值被标记为expunged,read中的entry拷贝到dirty时,会将key标记为expunged,需要手动解锁
   m.mu.Lock()
   read, _ = m.read.Load().(readOnly)
   // read map存在 但是expunged,就先拷贝到dirty map,存储value
   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)
   // read map 不存在, dirty map 存在,直接存value
   } else if e, ok := m.dirty[key]; ok {
      e.storeLocked(&value)
   // read map 不存在, dirty map 也不存在,在dirty新建一个entry存储,并关联read map
   } 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()
         m.read.Store(readOnly{m: read.m, amended: true})
      }
      m.dirty[key] = newEntry(value)
   }
   m.mu.Unlock()
}
  1. key原先就存在于read中,获取key所对应内存地址,原子性修改

  2. key存在,但是key所对应的值被标记为 expunged,解锁,解除标记,并更新dirty中的key,与read中进行同步,然后修改key对应的值

  3. read中没有key,但是dirty中存在这个key,直接修改dirty中key的值

  4. read和dirty中都没有值,先判断自从read上次同步dirty的内容后有没有再修改过dirty的内容,没有的话,先同步read和dirty的值,然后添加新的key value到dirty上面

Load操作

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
  // 如果read的map中没有,且存在修改
    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.)
    // 再查找一次,有可能刚刚将dirty升级为read了
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        if !ok && read.amended {
      // 如果amended 还是处于修改状态,则去dirty中查找
            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.
      // 增加misses的计数,在计数达到一定规则的时候,触发升级dirty为read
            m.missLocked()
        }
        m.mu.Unlock()
    }
  // read dirty中都没有找到
    if !ok {
        return nil, false
    }
  // 找到了,通过load判断具体返回内容
    return e.load()
}

func (e *entry) load() (value interface{}, ok bool) {
    p := atomic.LoadPointer(&e.p)
  // 如果p为nil或者expunged标识,则key不存在
    if p == nil || p == expunged {
        return nil, false
    }
    return *(*interface{})(p), true
}
  1. read map 没找到,但是为amended 修改状态

    2.再读一次,看是否dirty更新到read,去read 读

    3.还是 不存在,修改状态

    就去dirty map中读,并增加missed计数,来触发dirty更新到read中操作

  2. 找不到返回

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容