Golang中sync/map
提供了并发读写map功能。这里面分析的源码基于go1.14.13 版本。
sync.Map的结构
1 2 3 4 5 6 7 8 9 10 11 type Map struct { mu Mutex read atomic.Value dirty map [interface {}]*entry misses int }
sync.Map结构体中read字段是atomic.Value
类型,底层是readOnly结构体 :
1 2 3 4 type readOnly struct { m map [interface {}]*entry amended bool }
read map和dirty map的value类型是*entry, entry结构体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var expunged = unsafe.Pointer(new (interface {}))type entry struct { p unsafe.Pointer }
新增和更新操作
对Map的操作可以分为四类:
Add key-value 新增key-value
Update key-value 更新key对应的value值
Get Key-value 获取Key对应的Value值
Delete Key 删除key
我们来看看新增和更新操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 func (m *Map) Store (key, value interface {}) { 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() } func (e *entry) tryStore (i *interface {}) bool { for { p := atomic.LoadPointer(&e.p) if p == expunged { return false } if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) { return true } } } func (e *entry) unexpungeLocked () (wasExpunged bool ) { return atomic.CompareAndSwapPointer(&e.p, expunged, nil ) } func (e *entry) storeLocked (i *interface {}) { atomic.StorePointer(&e.p, unsafe.Pointer(i)) } func (m *Map) dirtyLocked () { if m.dirty != nil { return } read, _ := m.read.Load().(readOnly) m.dirty = make (map [interface {}]*entry, len (read.m)) for k, e := range read.m { if !e.tryExpungeLocked() { m.dirty[k] = e } } } func (e *entry) tryExpungeLocked () (isExpunged bool ) { p := atomic.LoadPointer(&e.p) for p == nil { if atomic.CompareAndSwapPointer(&e.p, nil , expunged) { return true } p = atomic.LoadPointer(&e.p) } return p == expunged }
获取Key-Value操作
接下来看看Map的Get操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 func (m *Map) Load (key interface {}) (value interface {}, 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() } func (e *entry) load () (value interface {}, ok bool ) { p := atomic.LoadPointer(&e.p) if p == nil || p == expunged { return nil , false } return *(*interface {})(p), true } func (m *Map) missLocked () { m.misses++ if m.misses < len (m.dirty) { return } m.read.Store(readOnly{m: m.dirty}) m.dirty = nil m.misses = 0 }
删除操作
在接着看看Map的删除操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func (m *Map) Delete (key interface {}) { 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 { delete (m.dirty, key) } m.mu.Unlock() } if ok { e.delete () } } func (e *entry) delete () (hadValue bool ) { for { p := atomic.LoadPointer(&e.p) if p == nil || p == expunged { return false } if atomic.CompareAndSwapPointer(&e.p, p, nil ) { return true } } }
遍历操作
sync.Map还提供遍历key-value功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func (m *Map) Range (f func (key, value interface {}) 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 = nil m.misses = 0 } m.mu.Unlock() } for k, e := range read.m { v, ok := e.load() if !ok { continue } if !f(k, v) { break } } }
总结
sync.Map是不能值传递的
sync.Map采用空间换时间策略。其底层结构存在两个map,分别是read map和dirty map。当读取操作时候,优先从read map中读取,是不需要加锁的,若key不存在read map中时候,再从dirty map中读取,这个过程是加锁的。当新增key操作时候,只会将新增key添加到dirty map中,此操作是加锁的,但不会影响read map的读操作
当sync.Map读取key操作时候,若从read map中一直未读到,若dirty map中存在read map中不存在的keys时,则会把dirty map升级为read map,这个过程是加锁的。这样下次读取时候只需要考虑从read map读取,且读取过程是无锁的
sync.Map中的dirty map要么是nil,要么包含read map中所有未删除的key-value
sync.Map适用于读多写少场景