目录

go sync.map

实现安全的map

自go 1.6之后, 并发地读写map会报错,这在一些知名的开源库中都存在这个问题,所以go 1.9之前的解决方案是额外绑定一个锁,封装成一个新的struct或者单独使用锁都可以。

直接加锁

type syncMap struct {
    items map[string]interface{}
    sync.RWMutex
}

读读不会阻塞,读写、写写会阻塞。

分段加锁

具体实现就是,基于上面的 syncMap 再包装一次,用多个 syncMap 来模拟实现一个 map:

type SyncMap struct {

    shards     []*syncMap
}

源码解析

type Map struct {
    mu Mutex
    read atomic.Value // readOnly  read map
    dirty map[interface{}]*entry  // dirty map
    misses int
}

总体图:

./增删改查整体图.png

查询:

./load.png

插入或更新:

./store.png

删除:

./delete.png

read map 的值是什么时间更新的 ?

Load/LoadOrStore/LoadAndDelete 时,当 misses 数量大于等于 dirty map 的元素个数时,会整体复制 dirty map 到 read map Store/LoadOrStore 时,当 read map 中存在这个key,则更新 Delete/LoadAndDelete 时,如果 read map 中存在这个key,则设置这个值为 nil。

dirty map 的值是什么时间更新的 ?

完全是一个新 key, 第一次插入 sync.Map,必先插入 dirty map Store/LoadOrStore 时,当 read map 中不存在这个key,在 dirty map 存在这个key,则更新 Delete/LoadAndDelete 时,如果 read map 中不存在这个key,在 dirty map 存在这个key,则从 dirty map 中删除这个key 当 misses 数量大于等于 dirty map 的元素个数时,会整体复制 dirty map 到 read map,同时设置 dirty map 为 nil

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。

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 赋值一遍

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。