看过这篇剖析,你还不懂 Go sync.Map 吗? (2)

疑问:当 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。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsszds.html