go源码分析: sync.Map

121 阅读2分钟

结构

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

type readOnly struct {
   m       map[interface{}]*entry
   amended bool // true if the dirty map contains some key not in m.
}

type entry struct {
   p unsafe.Pointer // *interface{}
}

写入

  1. read转为readOnly
  2. 如果在read中存在,且不是expunged(即没有标记为删除),则使用CAS更新entry.p,成功则返回
  3. 加锁,再读取一次read(dobule check)
  4. 如果在read中存在,且是expunged,则设置为nilentry写入dirty,值原子写入entry.p
  5. 如果在dirty中存在,则原子写入entry.p
  6. 如果都不存在
    1. 如果amendedfalse,且dirtynil,新建dirty,复制read中的非expungeddirtyread中为nil的被设置为expunged
    2. 原子设置amendedtrue
    3. 新建entry,值写入dirty
  7. 解锁

总结:entry.pnilentry一定同时存在readdirty中,原子修改entry.p即可。entry.pexpunged,则在dirty中不存在,需要额外一步,将entry写入dirty

读取

  1. read转为readOnly
  2. 如果存在,且不是expunged也不是nil,则返回存在,否则返回不存在
  3. 如果不存在,且amendedtrue
    1. 加锁,重新读取read,如果仍然不存在,且amendedtrue,从dirty中读取
    2. 增加misses计数,如果misses大于或等于len(m.dirty)dirty的map交换给readdirty设为nil,清空missesamended设为false
    3. 解锁
  4. 如果dirty中也不存在,则返回不存在

总结:先读read,再读dirtyread多次不命中,交换readdirty

删除

  1. read转为readOnly
  2. 如果不存在,且amendedfalse,则直接返回
  3. 如果不存在,且amendedtrue
    1. 加锁,如果仍然不存,且amendedtrue,从dirty中删除,存在则第4步
    2. 增加misses计数...
    3. 解锁
  4. 如果在read中或者dirty中存在,则CAS设置entry.p设为nil

总结:在read中假删,在dirty中真删

遍历

  1. read转为readOnly
  2. 如果amendedtrue,交换readdirty的map,dirty的map置nil
  3. 遍历read中所有非nilexpunged的数据

总结:readdirty不同步,则先同步。依靠read的只读性,可以进行安全的遍历、修改、删除。期间插入的数据不可见