Last updated
Last updated
和 JAVA 中 concurrent hashmap
分段的设计思路不同,sync.Map
的主要思想是做读写分离,底层用了2个 map
,read
和 dirty
,将频繁变更的数据,和极少变更的数据分离。
极少变更的数据放到 read
里,读 read
无需加锁,更新 read
依靠 CAS
也不用加锁,性能极高。
新增的数据放到 dirty
里。这类数据的读写需要加锁。
读取数据时,先查 read
,再查 dirty
,当读 read
cache miss 累计到一定次数时,说明该 key
读多写少了,将数据从 dirty
移入 read
read的数据结构 readOnly:
entry
p有三种值:
nil: entry已被删除了,并且m.dirty为nil
expunged: entry已被删除了,并且m.dirty不为nil,但这个entry在于m.dirty中
其它:entry是一个正常的值
先查找 read
,read
中没有再查找 dirty
、并增加 cache misses
计数
cache misses
数量累计至 dirty
的长度后,将 dirty
所有数据迁移至 read
中,并清空 dirty
如果 read
中已有 key
,通过 CAS
来更新值
新增 key
,或者更新 dirty
中的数据,需要加锁并写入 dirty
。其中新增 key
时还会把 read
中的 kv 倒灌回 dirty
和新增差不多,只不过不设置值了,而是调用底层 map
的 delete
方法
仅建议用于如下两种场景:
写一次,读多次。比如程序初始化时加载配置。
多协程环境下,不同协程读写不同的key。
这两种场景下,相比于使用原生map + 全局锁的情况,sync.Map
能显著优化加锁带来的性能开销。
如果是读写穿插的情况,会频发触发 dirtyLocked
函数,里面会遍历 map
的 key
并进行迁移,效率非常差。
既然读写 read
可以 CAS
,那只要 read
不要 dirty
可不可以?
那同原生 map
就没有区别了。sync.Map
中,read
仅用于读和更新已有 key
(这俩场景才能用 CAS
),无法用于新增 key
和触发扩容,后者的场景就需要用到 dirty
了。