entry 标记
expunged
: 标记 readonly
中的数据不可用
nil
: 标记 readonly
中的数据存在
存储逻辑
- 如果
key
在readonly
中存在- 如果
entry
未被标记为expunged
, 则直接进行指针交换 - 如果被标记为
expunged
, 则执行unexpungeLocked
将标记变更为nil
, 再在dirty
中 维护key
=>entry
- 如果
- 如果要存储的
key
在dirty
和readonly
中 都不存在- 如果
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
}
删除逻辑
- 在
readonly
中存在的- 通过
entry
delete 将p
设置为nil
- 下次
Store
方法命中readonly
, 尝试调用tryStore
交换entry
的p
- 通过
- 在
readonly
不存在,但是amended = true (标记dirty已构建)
- 从
dirty
中 删除 - 使用
dirty
到达一定次数后,也会将dirty的数据移动到readonly
- 从
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
对读多写少的场景比较友好