Architecture
图 1:sync.Map 主要结构关系图
sync.Map 是一种并发安全的 map 结构,其中核心字段及其关系如图 1 所示。
sync.Map 使用读写分离的思想结合内置 map 加锁实现了并发安全,其中 read 字段负责常用数据的存储,dirty 字段负责非常用数据的存储,misses 用来判断是否将 dirty 内存储的数据刷新到 read,具体结构如下所示。
type Map struct {mu Mutexread atomic.Value // readOnlydirty map[any]*entrymisses int}
sync.Map 通过使用 atomic.Value 原子类型来保证了常用数据存储操作的并发安全,因其与本章内容关系不大,因此在这里不做详细展开,只需了解 read 的类型实际上是 sync.readOnly 即可。
type readOnly struct {m map[any]*entryamended bool}
amended 字段是用来判断数据是否是只存储在 dirty 中的 flag,同时可以注意到最终存储到 map 中的都是 entry 结构,其 p 字段存储了最终数据的指针,p 有 nil、expunged 和 otherwise 三种状态,其中 expunged 用来作为 entry 已经被 dirty 删除的标记。
var expunged = unsafe.Pointer(new(any))type entry struct {p unsafe.Pointer}
Method
Load
sync.Map 提供了如 Load、Store、Delete、Range 等许多基础方法,让 sync.Map 的使用变得像普通 map 一样简单,下面是 Load 方法的执行逻辑。
func (m *Map) Load(key any) (value any, ok bool) {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 {e, ok = m.dirty[key]m.missLocked()}m.mu.Unlock()}if !ok {return nil, false}return e.load()}
sync.Map 加载数据的情况共分为三种,L14 ~ L16 是 read 和 dirty 都未命中的情况;L17 是直接在 read 中命中数据的情况;L4 ~ L13 是指在 read 中读取不到但是在 dirty 中能够读取到数据的情况,这时会直接在 dirty 中读取数据,这里需要注意的是 L10 的函数,这个函数的作用是刷新 misses 字段的值,同时如果满足一定条件会将 dirty 中的数据刷新到 read 中。
func (m *Map) missLocked() {m.misses++if m.misses < len(m.dirty) {return}m.read.Store(readOnly{m: m.dirty})m.dirty = nilm.misses = 0}
Store
L3 ~ L5 是指在 read 中命中并且 entry 未被删除的情况直接存储在 read 中;L8 ~ L12 是将 read 中命中的 entry 存储到 dirty 中并将数据存储到 entry;L13 ~ L14 是在 dirty 命中的 entry 中存储数据;L15 ~ L22 是将 dirty 初始化并将数据存储到 dirty 中。
func (m *Map) Store(key, value any) {read, _ := m.read.Load().(readOnly)if e, ok := read.m[key]; ok && e.tryStore(&value) {return}m.mu.Lock()read, _ = m.read.Load().(readOnly)if e, ok := read.m[key]; ok {if e.unexpungeLocked() {m.dirty[key] = e}e.storeLocked(&value)} else if e, ok := m.dirty[key]; ok {e.storeLocked(&value)} else {if !read.amended {m.dirtyLocked()m.read.Store(readOnly{m: read.m, amended: true})}m.dirty[key] = newEntry(value)}m.mu.Unlock()}
Delete
Delete 方法调用了 LoadAndDelete 方法,L8 ~ L18 是在数据只存在 dirty 中的情况下根据 key 值将entry 删除,同时尝试将最新数据刷新到 read;L19 ~ L21 是指在 read 中命中 entry,然后将 entry 中数据直接删除。
func (m *Map) Delete(key any) {m.LoadAndDelete(key)}func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {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 {e, ok = m.dirty[key]delete(m.dirty, key)m.missLocked()}m.mu.Unlock()}if ok {return e.delete()}return nil, false}
Range
Range 方法允许使用者提供处理数据的函数并通过一个 bool 类型来控制 sync.Map 遍历的中断,L3 ~ L13 是指在数据只存在于 dirty 的情况下将数据刷新到 read 中;L5 ~ L23 是真正执行 Range 函数使用者自定义数据处理函数的位置。
func (m *Map) Range(f func(key, value any) bool) {read, _ := m.read.Load().(readOnly)if read.amended {m.mu.Lock()read, _ = m.read.Load().(readOnly)if read.amended {read = readOnly{m: m.dirty}m.read.Store(read)m.dirty = nilm.misses = 0}m.mu.Unlock()}for k, e := range read.m {v, ok := e.load()if !ok {continue}if !f(k, v) {break}}}
