func(m *Map)Store(key, value interface{}) { read, _ := m.read.Load().(readOnly) // 首先尝试直接cas修改,如果该key存在于dirty中,则可以成功设置 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() { // key存在于read中,但是不在dirty中,加入dirty m.dirty[key] = e } // 直接atomic set e.storeLocked(&value) } elseif e, ok := m.dirty[key]; ok { // 存在dirty中,但是不在readonly中 e.storeLocked(&value) } else { // key还不存在 if !read.amended { // lazy init dirty m.dirtyLocked() m.read.Store(readOnly{m: read.m, amended: true}) } // 添加到dirty中 m.dirty[key] = newEntry(value) } m.mu.Unlock() }
read, _ := m.read.Load().(readOnly) m.dirty = make(map[interface{}]*entry, len(read.m)) for k, e := range read.m { if !e.tryExpungeLocked() { // 如果key未删除,将其加入dirty m.dirty[k] = e } } }
// 如果key的值为nil,则认为已经删除了,将其设置为expunged func(e *entry)tryExpungeLocked()(isExpunged bool) { p := atomic.LoadPointer(&e.p) for p == nil { if atomic.CompareAndSwapPointer(&e.p, nil, expunged) { returntrue } p = atomic.LoadPointer(&e.p) } return p == expunged }
// cas操作 func(e *entry)tryStore(i *interface{})bool { for { p := atomic.LoadPointer(&e.p) // dirty中不存在该key,需要竞争锁添加到dirty中 if p == expunged { returnfalse } if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) { returntrue } } }
// Delete deletes the value for a key. func(m *Map)Delete(key interface{}) { // 首先从read中查找要删除的entry 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 { // 直接从dirty中删除 delete(m.dirty, key) } m.mu.Unlock() } if ok { e.delete() } }
func(e *entry)delete()(hadValue bool) { // cas标记删除 for { p := atomic.LoadPointer(&e.p) if p == nil || p == expunged { returnfalse } if atomic.CompareAndSwapPointer(&e.p, p, nil) { returntrue } } }
总结
现在再来看源码注释:
The Map type is optimized for two common use cases: (1) when the entry for a given key is only ever written once but read many times, as in caches that only grow, or (2) when multiple goroutines read, write, and overwrite entries for disjoint sets of keys. In these two cases, use of a Map may significantly reduce lock contention compared to a Go map paired with a separate Mutex or RWMutex.