Go sync.Map

entry 标记

expunged: 标记 readonly 中的数据不可用
nil: 标记 readonly 中的数据存在

存储逻辑

  • 如果 keyreadonly 中存在
    • 如果 entry 未被标记为 expunged, 则直接进行指针交换
    • 如果被标记为 expunged, 则执行 unexpungeLocked 将标记变更为 nil, 再在 dirty 中 维护 key=> entry
  • 如果要存储的 keydirtyreadonly 中 都不存在
    • 如果 dirty还未进行初始化,则使用 readonly 中未标记为删除的元素来构建 dirty
    • key 及其对应的 entry 设置在 dirty
  • 如果数据只在 dirty 中存在,那么只维护 dirty 中的
 func (m *Map) Store(key, value any) {
	read, _ := m.read.Load().(readOnly)
    //要设置的key已经在readonly中存在,那么尝试更改entery的指向
	if e, ok := read.m[key]; ok && e.tryStore(&value) {
		return
	}

    /**
	 两种情况走下面的逻辑
	 1.要设置的key在readonly中存在,但是标记为删除
	 2.key在readonly中不存在
     */
	m.mu.Lock()
	read, _ = m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok {
		if e.unexpungeLocked() {
			/**
			这个值在readonly中存在,且是标记删除,那么就在dirty中存储一份,将entry的标记移除改为nil,那么下次还是能从readonly中获取
			 */
			m.dirty[key] = e
		}
        //entry的具体值也要更改
		e.storeLocked(&value)
	} else if e, ok := m.dirty[key]; ok {
        //readonly中不存在,在dirty中,重新设置dirty的值,反正一定次数后,dirty会覆盖readonly
		e.storeLocked(&value)
	} else {
        //构建dirty
		if !read.amended {
            //将只读中的未标记为删除的entry拿出来构建dirty
			m.dirtyLocked()
            //设置只读的dirty构建完毕
			m.read.Store(readOnly{m: read.m, amended: true})
		}
        //只在dirty维护新的entry
		m.dirty[key] = newEntry(value)
	}
	m.mu.Unlock()
}


func (e *entry) tryStore(i *any) bool {
	for {
		p := atomic.LoadPointer(&e.p)
        //要设置的key被标记为删除
		if p == expunged {
			return false
		}
        //如果不是被标记为删除,交换真实数据的指针地址
		if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
			return true
		}
	}
}

// unexpungeLocked ensures that the entry is not marked as expunged.
//
// If the entry was previously expunged, it must be added to the dirty map
// before m.mu is unlocked.
func (e *entry) unexpungeLocked() (wasExpunged bool) {
	return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}

读取逻辑

  • 如果 readonly 中不存在,且 dirty 也没有构建 或者在 dirty 中也不存在,返回 nil
  • 如果 readonly 中不存在,但是 dirty 已构建 且在 dirty 中存在该 key
    • dirty 返回该 key 对应的 entry 的值
    • dirty 中获取一定次数后,使用 dirty 重新构建 readonly
func (m *Map) Load(key any) (value any, ok bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
	if !ok && read.amended {
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
			e, ok = m.dirty[key]
            //readonly中取不到,从dirty获取达到一定次数后,讲dirty的数据覆盖readonly的数据
			m.missLocked()
		}
		m.mu.Unlock()
	}
	if !ok {
		return nil, false
	}
    //在readonly或者dirty中存在,都会执行这里返回一个具体的值
	return e.load()
}

func (m *Map) missLocked() {
	m.misses++
	if m.misses < len(m.dirty) {
		return
	}
    //从dirty中获取数据的次数,比dirty本身存储的数据都要多,那么使用dirty构建readonly
    //并
	m.read.Store(readOnly{m: m.dirty})
	m.dirty = nil
	m.misses = 0
}

删除逻辑

  1. readonly 中存在的
    1. 通过 entry delete 将 p 设置为 nil
    2. 下次 Store 方法命中 readonly, 尝试调用 tryStore 交换 entryp
  2. readonly 不存在,但是 amended = true (标记dirty已构建)
    1. dirty 中 删除
    2. 使用 dirty 到达一定次数后,也会将dirty的数据移动到 readonly
  3. readonly, dirty 中都没有,那么直接返回 nil,false
// LoadAndDelete deletes the value for a key, returning the previous value if any.
// The loaded result reports whether the key was present.
func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
    //在readonly中不存在且dirty已经构建
	if !ok && read.amended {
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
            //从dirty中移除,如果使用dirty到达次数,还是会将dirty数据移到readonly
			e, ok = m.dirty[key]
			delete(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()
	}

    //删除entry,将entrp p设置为 nil,下次如果通过store的tryStore命中,p会直接交换
	if ok {
		return e.delete()
	}
	return nil, false
}

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
		}
	}
}

总结

首先,这个设计有点像二级缓存,其次基本上所有方法都有双检查(第一次不加锁,第二次加锁),从这两方面来看,sync.map 对读多写少的场景比较友好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值