sync.Map分析(1.20)
sync.Map
sync.Map是Go语言标准库中提供的并发安全的映射(Map)类型。它在多个Goroutine之间提供了安全的并发访问和修改映射的能力,适用于高并发的场景。
sync.Map的一些特点
- 并发安全:
sync.Map
内部使用细粒度的锁机制
- 无需显式锁:与传统互斥锁不同,
sync.Map
不需要每次在读写操作时显式加锁和解锁
- 动态增长:
sync.Map
的容量会按需要动态增长
- 值类型限制:
sync.Map
键和值可以是任意类型,但是键的类型必须的课比较的(comparale)。因为内部查找和操作元素需要键的比较。否则会panic: panic: runtime error: hash of unhashable type map[int]string
- 基本操作:
sync.Map
提供了一些基本的操作方法,如Store
、Load
、LoadOrStore
、Delete
和Range
等。
Store(key, value)
:存储键值对到映射中。
Load(key)
:加载指定键对应的值。
LoadOrStore(key, value)
:加载指定键对应的值,如果键不存在则存储给定的键值对。
Delete(key)
:删除指定键值对。
Range(func(key, value interface{}) bool)
:遍历映射并对每个键值对执行指定的函数。
- 无序:
sync.Map
的方法Range
在遍历时是无序的
使用示例:
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
| package main
import ( "fmt" "sync" )
func main() { var m sync.Map
m.Store("key1", "value1") m.Store("key2", "value2")
value1, ok := m.Load("key1") if ok { fmt.Println("Value1:", value1) }
value3, loaded := m.LoadOrStore("key3", "value3") if loaded { fmt.Println("Loaded Value3:", value3) } else { fmt.Println("Stored Value3:", value3) }
m.Delete("key2")
m.Range(func(key, value interface{}) bool { fmt.Println("Key:", key, "Value:", value) return true }) }
|
深入了解(1.20版本)
sync.Map的数据结构
1 2 3 4 5 6 7 8 9 10
| type Map struct { mu Mutex read atomic.Pointer[readOnly] dirty map[any]*entry misses int }
|
readOnly的数据结构
1 2 3 4 5 6
| type readOnly struct { m map[any]*entry amended bool }
|
entry的数据结构
1 2 3 4
| type entry struct { p atomic.Pointer[any] }
|
细节:nil和expunged两种状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| nil的状态来源为read中删除元素,万能指针p被标记删除为nil - 在store中,如果key被标记为nil,那么可以重新赋值使用,可以绕过dirty赋值和misses字段更新 - 如果被标记为expunged,那么只能在dirty中新增数据,然后更新misses
expunged:在store时,read和dirty都不存在该key,就必须在dirty新增数据,将数据返回。 在dirty新增数据过程中,会重新将read的数据放入dirty(标记为nil的p则会被标记为expunged,标记为expunged则不放入dirty,那么被标记为expunged的字段,会就在delete方法中被直接删除)
万能指针 :删除-> nil -> expunged ->被删除(调用delete),这是一个数据被删除的其中过程。 而nil的状态,可以让Store方法减少dirty更新流程,达到复用key的作用。(直接使用使用一种状态的话,那就只有删除和未删除的状态,必须涉及map增减)
func (e *entry) trySwap(i *any) (*any, bool) { for { p := e.p.Load() if p == expunged { return nil, false } if e.p.CompareAndSwap(p, i) { return p, true } } }
|
Delete方法
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
|
func (m *Map) LoadAndDelete(key any) (value any, loaded bool) { read := m.loadReadOnly() e, ok := read.m[key] if !ok && read.amended { m.mu.Lock() read = m.loadReadOnly() 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 }
func (e *entry) delete() (value any, ok bool) { for { p := e.p.Load() if p == nil || p == expunged { return nil, false } if e.p.CompareAndSwap(p, nil) { return *p, true } } }
|
Store方法
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 84 85 86 87 88 89 90 91 92 93 94
|
func (m *Map) Swap(key, value any) (previous any, loaded bool) { read := m.loadReadOnly() if e, ok := read.m[key]; ok { if v, ok := e.trySwap(&value); ok { if v == nil { return nil, false } return *v, true } } m.mu.Lock() read = m.loadReadOnly() if e, ok := read.m[key]; ok { if e.unexpungeLocked() { m.dirty[key] = e } if v := e.swapLocked(&value); v != nil { loaded = true previous = *v } } else if e, ok := m.dirty[key]; ok { if v := e.swapLocked(&value); v != nil { loaded = true previous = *v } } else { if !read.amended { m.dirtyLocked() m.read.Store(&readOnly{m: read.m, amended: true}) } m.dirty[key] = newEntry(value) } m.mu.Unlock() return previous, loaded }
func (e *entry) trySwap(i *any) (*any, bool) { for { p := e.p.Load() if p == expunged { return nil, false } if e.p.CompareAndSwap(p, i) { return p, true } } }
func (m *Map) dirtyLocked() { if m.dirty != nil { return } read := m.loadReadOnly() m.dirty = make(map[any]*entry, len(read.m)) for k, e := range read.m { if !e.tryExpungeLocked() { m.dirty[k] = e } } }
func (e *entry) tryExpungeLocked() (isExpunged bool) { p := e.p.Load() for p == nil { if e.p.CompareAndSwap(nil, expunged) { return true } p = e.p.Load() } return p == expunged }
|
Load方法
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
| func (m *Map) Load(key any) (value any, ok bool) { read := m.loadReadOnly() e, ok := read.m[key] if !ok && read.amended { m.mu.Lock() read = m.loadReadOnly() 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 (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 }
|
Delete方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func (m *Map) LoadAndDelete(key any) (value any, loaded bool) { read := m.loadReadOnly() e, ok := read.m[key] if !ok && read.amended { m.mu.Lock() read = m.loadReadOnly() 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 }
|
sync.Map是通过冗余的两个数据结构(read、dirty),实现性能的提升。为了提升性能,load、delete、store等操作 尽量使⽤只读的read;为了提⾼read的key击中概率,采⽤动态调整,将dirty数据提升为read;对于数据的删除, 采⽤延迟标记删除法,只有在提升dirty的时候才删除。