sync.Map 是go语言标准库实现的并发安全的map;
0x01 使用场景
下面的两个场景可以减少锁使用
- 读多写少场景
- 多个协程读写,覆盖key不想交的场景(key不重合)
0x02 代码实现
代码的设计逻辑为双map,分为read Map 和dirty Map。两个map协作,对外提供CRUD功能的时候会出现下面的情况,有一个有,一个没有,或者双缺的情况。那么出现dirty和read 都有对应key的情况下谁的数据为准呢?下面具体看下代码实现。
//只读map定义
type readOnly struct {
m map[interface{}]*entry
//dirty中有,但是read中没有的情况。
amended bool // true if the dirty map contains some key not in m.
}
type Map struct {
//互斥锁
mu Mutex
//对象为上面定义的readOnly 结构体
read atomic.Value // readOnly
//脏map
dirty map[interface{}]*entry
//缺失数
misses int
}
//read 场景
//load key 逻辑
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
//1. 先去read中读取,read中有,直接返回,不需要去dirty取
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
if !ok && read.amended {
m.mu.Lock()
//read没有,并且read中缺失了新写入的key
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
//去dirty中读
e, ok = m.dirty[key]
//下面的函数记录一次miss,但是当miss的次数
//赶上dirty的key的数量的时候,直接将dirty的map
//切到read中
m.missLocked()
}
m.mu.Unlock()
}
//read,dirty双缺的情况下返回false
if !ok {
return nil, false
}
//返回具体值
return e.load()
}
//insert/update key 逻辑
// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
//read 有 , dirty没有场景
//1.先去read map读取值,如果找到了,直接更新,这里的更新(tryStore)
//用了原子操作
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
//2. 没有找到,上锁,如果在临界区中在read中找到key
//那么更新key的值,并更新dirty的中值
m.mu.Lock()
//read有dirty没有场景
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {
m.dirty[key] = e
}
e.storeLocked(&value)
//在read中没有找到,在dirty中找到了,更新dirty中的值
} else if e, ok := m.dirty[key]; ok {//dirty有,read没有场景
e.storeLocked(&value)
} else {//双缺场景
//在read和dirty map中都没有找到的情况想,如果此刻read中的缺失标记
//为false,那么将read中的缺失标记设置为true,并将read中的值拷贝到dirty中。
if !read.amended {
//下面这行代码将read的中的值copy到dirty中,这里为什么要这样做呢,
//因为在这个函数的开头部分,可以看到read中的已有key是可以直接被更新的
//这里就需要dirty 和 read保持一致,并让dirty 以read的值为准
m.dirtyLocked()
//更新read的值,将缺失设置为true,因为,read和dirty 双缺的情况下
//第一次只在dirty中加入了新key。
m.read.Store(readOnly{m: read.m, amended: true})
}
//赋值dirty map
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
//delete key
// 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 interface{}) (value interface{}, loaded bool) {
//去read中读
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 {
//在dirty找到,在read中没有;清理掉dirty中的key
e, ok = m.dirty[key]
delete(m.dirty, key)
//记录一次miss
m.missLocked()
}
m.mu.Unlock()
}
if ok {
return e.delete()
}
//双缺
return nil, false
}
0x03 总结
- read 和dirty map,以read map中的记录为准
- dirty map中的数据在miss次数大于dirty的大小的时候,dirty map数据会直接更新到read中
- 插入新key的时候并不会立即更新read,而会插入dirty map,并记录read map有缺失和dirty不一致
- 在插入新值的时候,由于不会立即插入到read中,但是会以read数据为准,所以插入dirty前会将read数据copy到dirty中,使两者在变化前保持一致。
- read 中的amended 变量只有在插入数据的时候才会设置为true,当删除read 中的key,没有删除dirty的中的key时,再读的时候即使在read中miss也不会去dirty中读。