go的sync.Map了解吗

题目来源:好未来

答案:

总体概述

sync.Map 采用读写分离和用空间换时间的策略保证 Map 的读写安全

Map 的基本结构

  1. type Map struct {
  2. mu Mutex
  3. read atomic.Value
  4. dirty map[ant]*entry
  5. misses int
  6. }

144.go的sync.Map了解吗 - 图1

read

read 使用 map[any]*entry 存储数据,本身支持无锁的并发读

read 可以在无锁的状态下支持 CAS 更新,但如果更新的值是之前已经删除过的 entry 则需要加锁操作

由于 read 只负责读取,dirty 负责写入,因此使用 amended 来标记 dirty 中是否包含 read 没有的字段

dirty

dirty 本身就是一个原生 map,需要加锁保证并发写入

entry

read 和 dirty 都是用到 entry 结构

entry 内部只有一个 unsafe.Pointer 指针 p 指向 entry 实际存储的值

指针 p 有三种状态

  • p == nil

    在此状态下对应: entry 已经被删除 或 map.dirty == nil 或 map.dirty 中有 key 指向 e 此处不明

  • p == expunged

    在此状态下对应:entry 已经被删除 或 map.dirty != nil 同时该 entry 无法在 dirty 中找到

  • 其他情况

    entry 都是有效状态并被记录在 read 中,如果 dirty 不为空则也可以在 dirty 中找到

Load

  1. func (m *Map) Load(key any) (value any, ok bool) {
  2. read, _ := m.read.Load().(readOnly)
  3. e, ok := read.m[key]
  4. // 如果 read 中读取不到,则尝试从 dirty 中读取
  5. if !ok && read.amended {
  6. m.mu.Lock()
  7. // 双重判定,防止枷锁过程中 dirty 被提升为 read
  8. read, _ = m.read.Load().(readOnly)
  9. e, ok = read.m[key]
  10. if !ok && read.amended {
  11. e, ok = m.dirty[key]
  12. // 尽管可以从 dirty 中获取数据,但被认为是 read 未击中且低效
  13. m.missLocked()
  14. }
  15. m.mu.Unlock()
  16. }
  17. // read 中读取不到且此时 dirty 包含的数据 <= read,直接返回
  18. // 此时 dirty 只有两种情况 要么为 nil,要么就是 read 的拷贝
  19. if !ok {
  20. return nil, false
  21. }
  22. return e.load()
  23. }

missLocked

  1. func (m *Map) missLocked() {
  2. m.misses++
  3. // 用长度来判断性能,如果 misses 大于 dirty 的长度,就认为继续拓展 dirty 不如重新 copy 一份
  4. if m.misses < len(m.dirty) {
  5. return
  6. }
  7. m.read.Store(readOnly{m: m.dirty})
  8. // 将 dirty 拓展为 read,原 dirty 设置为 nil(此处应该是与前文相对)
  9. // 如果 dirty 为 nil,则下次写入时会将 dirty 初始化为一份 read 的浅拷贝
  10. m.dirty = nil
  11. m.misses = 0
  12. }

Store

  1. func (m *Map) Store(key, value any) {
  2. // 如果存储的 key 和 value 已经存在直接返回即可
  3. read, _ := m.read.Load().(readOnly)
  4. if e, ok := read.m[key]; ok && e.tryStore(&value) {
  5. return
  6. }
  7. m.mu.Lock()
  8. read, _ = m.read.Load().(readOnly)
  9. if e, ok := read.m[key]; ok {
  10. // 当前值没有被删除过写入 dirty
  11. if e.unexpungeLocked() {
  12. m.dirty[key] = e
  13. }
  14. e.storeLocked(&value)
  15. } else if e, ok := m.dirty[key]; ok {
  16. // 当前 key 在 dirty 中存在值,直接更新
  17. e.storeLocked(&value)
  18. } else {
  19. // key-value 不在 read 也不在 dirty 中,修改 read.amdended,并将 k-v 存入 dirty
  20. if !read.amended {
  21. m.dirtyLocked()
  22. m.read.Store(readOnly{m: read.m, amended: true})
  23. }
  24. m.dirty[key] = newEntry(value)
  25. }
  26. m.mu.Unlock()
  27. }

Delete

  1. func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
  2. read, _ := m.read.Load().(readOnly)
  3. e, ok := read.m[key]
  4. // 要删除的值不在 read 中
  5. if !ok && read.amended {
  6. m.mu.Lock()
  7. // 双重判断,
  8. read, _ = m.read.Load().(readOnly)
  9. e, ok = read.m[key]
  10. if !ok && read.amended {
  11. e, ok = m.dirty[key]
  12. // 直接删除 dirty 中的键值
  13. delete(m.dirty, key)
  14. // 虽然此处时删除操作,但是还是通过抵消(dirty)途径访问到的,因此对 misses++
  15. m.missLocked()
  16. }
  17. m.mu.Unlock()
  18. }
  19. // 值在 read 可以直接找到,则直接删除
  20. if ok {
  21. return e.delete()
  22. }
  23. return nil, false
  24. }

Range

  1. func (m *Map) Range(f func(key, value any) bool) {
  2. // 如果 read 中 amended 字段为 false 则直接遍历 read 就可以获取到所有数据
  3. read, _ := m.read.Load().(readOnly)
  4. if read.amended {
  5. m.mu.Lock()
  6. read, _ = m.read.Load().(readOnly)
  7. if read.amended {
  8. // 由于 dirty 存储了部分 read 中没有的数据,因此在遍历时直接将 dirty 提升为 read
  9. read = readOnly{m: m.dirty}
  10. m.read.Store(read)
  11. m.dirty = nil
  12. m.misses = 0
  13. }
  14. m.mu.Unlock()
  15. }
  16. for k, e := range read.m {
  17. v, ok := e.load()
  18. if !ok {
  19. continue
  20. }
  21. if !f(k, v) {
  22. break
  23. }
  24. }
  25. }