sync.Map 源码分析
- sync.Map 结构
type Map struct {
mu Mutex
read atomic.Value // readOnly
dirty map[interface{}]*entry
misses int // 未命中计数器
}
type readOnly struct {
m map[interface{}]*entry
amended bool // 为true时表示 Map.dirty 中是否包含 m 中不存在的 key
}
- 从源码中可以看出, Map 结构体中包含2个原生map, 分别是 Map.dirty 和 Map.read
- Map.read 是个readOnly 类型的结构体,其内包含一个 map 和Map.dirty 一样,都是key到*value的映射
- Map.read 这个字典是只读的, 查询不需要加锁,只读字典不会增减其中的键值,但是可以修改键对应的值
- sync.Map.Load 取数据
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
if !ok && read.amended {
m.mu.Lock()
// 加锁后重复查找 Map.read 的原因是,防止在加锁之前,
// Map.dirty 提升成了 Map.read,这样的话,直接去查Map.dirty肯定也查不到
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
e, ok = m.dirty[key]
// 累加未命中计数器,并检查Map.dirty是否需要升级为Map.read,
// 如需要则升级,否则,直接返回
m.missLocked()
}
m.mu.Unlock()
}
if !ok {
return nil, false
}
return e.load()
}
- Load 时,先去 Map.read 字典中查找,如果存在,则返回对应值,如果不存在,加锁在查一遍,还是不存在,再去Map.dirty中查找
- 第二次查找 Map.read 的原因是,反正在加锁之前,Map.dirty 提成成了 Map.read,这样的话,直接去查Map.dirty肯定也查不到,就会造成bug
- 无论 Map.dirty 中是否存在对应的键,都会调用missLocked函数
- Map.missLocked 这个函数是在加锁的状态下执行的
func (m *Map) missLocked() {
// 未命中数加1
m.misses++
if m.misses < len(m.dirty) {
return
}
// 当未命中数 大于等于 dirty 的长度是, dirty 变成 m.read
m.read.Store(readOnly{m: m.dirty})
// 清空 dirty 和 未命中计数器
m.dirty = nil
m.misses = 0
}
- 首先,未命中数加1
- 当未命中数小于Map.dirty的长度时,直接返回,否则,将Map.dirty提升为Map.read,并将Map.dirty和Map.misses重置
- Map.Store 存数据
func (m *Map) Store(key, value interface{}) {
// 先去Map.read中检查key是否存在,
// 若存在,则直接修改key的值为value并返回(Map.read的key不能修改,value是可以修改的)
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
// Map.read中不存在, 需要加锁去Map.dirty中查找
m.mu.Lock()
// 这里和Load的逻辑一样,加锁后再次查找,防止加锁之前Map.dirty提升为Map.read
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
// 加锁后再Map.read中查到了, 需要修改这个key对应的值
if e.unexpungeLocked() {
// 该key不在Map.dirty上, 在Map.dirty上保存该key
m.dirty[key] = e
}
// 修改key对应的值
e.storeLocked(&value)
} else if e, ok := m.dirty[key]; ok {
// 在Map.dirty中查到了该key,直接修改即可,这里用到的是原子操作
e.storeLocked(&value)
} else {
// read未被修正过
if !read.amended {
// 使用Map.read的键值初始化Map.dirty
m.dirtyLocked()
// 标记Map.read不完整, 意思就是Map.read没有包含全部的key和value,
// Map.read的key不能修改, 只能通过 Map.dirty 升级来替换 Map.read
m.read.Store(readOnly{m: read.m, amended: true})
}
// 将值存入Map.dirty, 因为这个key在Map.read中不存在,所以上边会把Map.read标记为不完整
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
- Map.LoadOrStore 取或者存数据
- 该函数的逻辑相当于是综合了Load函数和Store函数, 这里就不赘述了
- Map.Delete 删除数据
func (m *Map) Delete(key interface{}) {
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 {
delete(m.dirty, key)
}
m.mu.Unlock()
}
if ok {
e.delete()
}
- 首先去 Map.read 中查询 key 是否存在,
- 若 key 不存在, 并且 Map.read 是不完整的,则加锁重复查找,找到执行delete,
- 找不到就直接在Map.dirty上执行delete操作
- Map.Range 遍历
func (m *Map) Range(f func(key, value interface{}) bool) {
read, _ := m.read.Load().(readOnly)
if read.amended {
// 如果Map.read是不完整的,即Map.dirty中存在Map.read中不存在的key,则将Map.dirty升级为Map.read
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
if read.amended {
read = readOnly{m: m.dirty}
m.read.Store(read)
m.dirty = nil
m.misses = 0
}
m.mu.Unlock()
}
// 迭代Map.read, 执行传入函数,当函数返回false时,结束迭代
for k, e := range read.m {
v, ok := e.load()
if !ok {
continue
}
if !f(k, v) {
break
}
}
}
- 该方法可以用来迭代Map中所有的元素, Map 不能直接用于for做迭代操作,要想遍历Map, 只能用该函数
- 该方法接收一个函数,该函数接收两个参数,分别是Map的key和value
- 迭代Map.read,当函数返回false时,结束迭代操作
- 总结
- sync.Map 并发安全的map
- sync.Map类型在内部使用了大量的原子操作来存取键和值,并使用了两个原生的map作为存储介质
- 其中一个原生map存储在sync.Map.read字段中,该字段是sync.atomic.Value类型的,该类型可以使用原子操作,所以访问read时不需要加锁
- 另一个原生map存储在sync.Map.dirty字段中,该字段的key类型是interface{}, value类型是*entry, 这是一个自定义的结构体,里面是一个指针类型的字段p
- 两个原生的map中存放的value都是真正值的指针,所以两份map的底层指向的是同一份数据,只是各自保存一份指针而已
- read的中的key是只读的,不能被修改,但是可以通过key修改value
- 无论是查询还是修改,都是先去read中操作,只有当read中不存在key时,才会去dirty中操作
- read中存在的key,dirty中一定存在,反之,就不一定了
- 每次取read中查询数据,如果差不到,不管 dirty中是否存在,sync.Map.misses(未命中数)都会加1
- 当sync.Map.misses大于等于dirty的长度时,将dirty升级成read,同时将sync.Map.misses置为0,dirty置为nil
- 存储新值的时候,如果dirty为nil, 则会遍历 read 赋值给 dirty,然后将新值存入 dirty, 并标记 read.amended 为 true (意思是dirty中存在read中不存在的值)
- 删除时,直接在dirty上执行delete函数,如果read中存在这个key,这将这个值置为nil(相当于是标记删除,read 中的key不能被修改)
-
sync.Map 中的 read 与 dirty
(图出自 极客时间-go语言36讲) -
sync.Map 中 read 与 dirty 的互换
(图出自 极客时间-go语言36讲)