源码解读 Golang 的 sync.Map 实现原理

简介

Go 的内建 map 是不支持并发写操作的,原因是 map 写操作不是并发安全的,当你尝试多个 Goroutine 操作同一个 map,会产生报错:fatal error: concurrent map writes

因此官方另外引入了 sync.Map 来满足并发编程中的应用。

sync.Map 的实现原理可概括为:

  • 通过 read 和 dirty 两个字段将读写分离,读的数据存在只读字段 read 上,将最新写入的数据则存在 dirty 字段上
  • 读取时会先查询 read,不存在再查询 dirty,写入时则只写入 dirty
  • 读取 read 并不需要加锁,而读或写 dirty 都需要加锁
  • 另外有 misses 字段来统计 read 被穿透的次数(被穿透指需要读 dirty 的情况),超过一定次数则将 dirty 数据同步到 read 上
  • 对于删除数据则直接通过标记来延迟删除

数据结构

Map 的数据结构如下:

type Map struct {
   
    // 加锁作用,保护 dirty 字段
    mu Mutex
    // 只读的数据,实际数据类型为 readOnly
    read atomic.Value
    // 最新写入的数据
    dirty map[interface{
   }]*entry
    // 计数器,每次需要读 dirty 则 +1
    misses int
}

其中 readOnly 的数据结构为:

type readOnly struct {
   
    // 内建 map
    m  map[interface{
   }]*entry
    // 表示 dirty 里存在 read 里没有的 key,通过该字段决定是否加锁读 dirty
    amended bool
}

entry 数据结构则用于存储值的指针:

type entry struct {
   
    p unsafe.Pointer  // 等同于 *interface{}
}

属性 p 有三种状态:

  • p == nil: 键值已经被删除,且 m.dirty == nil
  • p == expunged: 键值已经被删除,但 m.dirty!=nilm.dirty 不存在该键值(expunged 实际是空接口指针)
  • 除以上情况,则键值对存在,存在于 m.read.m 中,如果 m.dirty!=nil 则也存在于 m.dirty

Map 常用的有以下方法:

  • Load:读取指定 key 返回 value
  • Store: 存储(增或改)key-value
  • Delete: 删除指定 key

源码解析

Load

func (m *Map) Load(key interface{
   }) (value interface{
   }, ok bool) {
   
    // 首先尝试从 read 中读取 readOnly 对象
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]

    // 如果不存在则尝试从 dirty 中获取
    if !ok && read.amended {
   
        m.mu.Lock()
        // 由于上面 read 获取没有加锁,为了安全再检查一次
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]

        
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值