疑问:当 dirty map 复制到 read map 后,将 dirty map 设置为 nil,也就是 dirty map 中就不存在这个 key 了。如果又新插入某个 key,多次访问后达到了 dirty map 往 read map 复制的条件,如果直接用 read map 覆盖 dirty map,那岂不是就丢了之前在 read map 但不在 dirty map 的 key ?
答:其实并不会。当 dirty map 向 read map 复制后,readOnly.amended 等于了 false。当新插入了一个值时,会将 read map 中的值,重新给 dirty map 赋值一遍,也就是 read map 也会向 dirty map 中复制。
func (m *Map) dirtyLocked() { if m.dirty != nil { return } read, _ := m.read.Load().(readOnly) m.dirty = make(map[interface{}]*entry, len(read.m)) for k, e := range read.m { if !e.tryExpungeLocked() { m.dirty[k] = e } } } read map 和 dirty map 是什么时间删除的?当 read map 中存在某个 key 的时候,这个时候只会删除 read map, 并不会删除 dirty map(因为 dirty map 不存在这个值)
当 read map 中不存在时,才会去删除 dirty map 里面的值
疑问:如果按照这个删除方式,那岂不是 dirty map 中会有残余的 key,导致没删除掉?
答:其实并不会。当 misses 数量大于等于 dirty map 的元素个数时,会整体复制 dirty map 到 read map。这个过程中还附带了另外一个操作:将 dirty map 置为 nil。
func (m *Map) missLocked() { m.misses++ if m.misses < len(m.dirty) { return } m.read.Store(readOnly{m: m.dirty}) m.dirty = nil m.misses = 0 } read map 与 dirty map 的关系 ?在 read map 中存在的值,在 dirty map 中可能不存在。
在 dirty map 中存在的值,在 read map 中也可能存在。
当访问多次,发现 dirty map 中存在,read map 中不存在,导致 misses 数量大于等于 dirty map 的元素个数时,会整体复制 dirty map 到 read map。
当出现 dirty map 向 read map 复制后,dirty map 会被置成 nil。
当出现 dirty map 向 read map 复制后,readOnly.amended 等于了 false。当新插入了一个值时,会将 read map 中的值,重新给 dirty map 赋值一遍
read/dirty map 中的值一定是有效的吗?并不一定。放入到 read/dirty map 中的值总共有 3 种类型:
nil:如果获取到的 value 是 nil,那说明这个 key 是已经删除过的。既不在 read map,也不在 dirty map
expunged:这个 key 在 dirty map 中是不存在的
valid:其实就正常的情况,要么这个值存在在 read map 中,要么存在在 dirty map 中
sync.Map 是如何提高性能的?通过源码解析,我们知道 sync.Map 里面有两个普通 map,read map主要是负责读,dirty map 是负责读和写(加锁)。在读多写少的场景下,read map 的值基本不发生变化,可以让 read map 做到无锁操作,就减少了使用 Mutex + Map 必须的加锁/解锁环节,因此也就提高了性能。
不过也能够看出来,read map 也是会发生变化的,如果某些 key 写操作特别频繁的话,sync.Map 基本也就退化成了 Mutex + Map(有可能性能还不如 Mutex + Map)。
所以,不是说使用了 sync.Map 就一定能提高程序性能,我们日常使用中尽量注意拆分粒度来使用 sync.Map。
关于如何分析 sync.Map 是否优化了程序性能,同样可以使用 pprof。具体过程可以参考 《这可能是最容易理解的 Go Mutex 源码剖析》
sync.Map 应用场景读多写少
写操作也多,但是修改的 key 和读取的 key 特别不重合。
关于第二点我觉得挺扯的,毕竟我们很难把控这一点,不过由于是官方的注释还是放在这里。
实际开发中我们要注意使用场景和擅用 pprof 来分析程序性能。
sync.Map 使用注意点和 Mutex 一样, sync.Map 也同样不能被复制,因为 atomic.Value 是不能被复制的。
参考链接https://golang.design/under-the-hood/zh-cn/part1basic/ch05sync/map/
https://draveness.me/golang-sync-primitives/
https://github.com/golang/go/blob/master/src/sync/map.go
sync.Map 完整流程图获取链接:链接: https://pan.baidu.com/s/16yEnZFbXwSe3qkvX1zi-Wg 密码: 8w8k。