Go 内置的map类型不是并发安全的,在并发场景中,会引发panic,而在Go编码中会经常在并发中使用map结构,所以Go 创建了sync.Map.
sync.Map主要使用场景是读多写少;下面使用m表示sync.Map
-
sync.Map 源码中包含的内容
var expunged = unsafe.Pointer(new(any)) //key 删除标志
entry: sync.Map 值类型
type readOnly struct { m map[any]*entry amended bool // 是否Map.dirty 一致 }
Map: 是一种并发安全的 map[any]*entry{}
-
entry 源码
entry表示map中存储的值
entry 结构体:
type entry struct {
/*
p 指向 interface{} 类型的值,用于保存 entry
如果p == nil,则 entry 已删除,但还存在于m中,且 m.dirty == nil
如果p == expunged,则entry 已被删除,m中已无该条目
否则,entry 仍然有效,且被记录在 m.read.m[key] ,但如果 m.dirty != nil,则在 m.dirty[key] 中
一个entry可以通过原子替换为nil来删除:当m.dirty下一次创建时,它将原子替换为deleted,并保留m.dirty[key]不设置。
与一个 entry 关联的值可以被原子替换式的更新,提供的 p != expunged。如果 p == expunged,则与 entry 关联的值只能在 m.dirty[key] = e 设置后被更新,因此会使用 dirty map 来查找 entry。
*/
p unsafe.Pointer // *interface{}
}
entry 方法:
//存储entry
func (e *entry) storeLocked(i *any) {
atomic.StorePointer(&e.p, unsafe.Pointer(i))
}
//获取值
func (e *entry) load() (value any, ok bool) {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return nil, false
}
return *(*any)(p), true
}
//删除值
func (e *entry) delete() (value any, ok bool) {
for {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return nil, false
}
if atomic.CompareAndSwapPointer(&e.p, p, nil) {
return *(*any)(p), true
}
}
}
其它方法:
newEntry(i any) *entry
func(e *entry) tryStore(i *any) bool //尝试存储
func(e *entry) unexpungeLocked() (wasExpunged bool) //设置删除
func(e *entry) storeLocker(i *any) //存储值
func(e *entry) tryLoadOrStore(i any) (acual any,loaded, ok bool) //尝试获取或存储
func(e *entry) tryExpungeLocked()(isExpungeLocked bool) //尝试设置expunged
entry包含一个指向interface{}的指针,表示m中key对应的值,通过 entry== nil|expunged 来处理被删除的entry,待GC时将entry进行回收.
3.Map 源码
type Map struct {
mu Mutex
read atomic.Value // readOnly 主要通过m.read实现"读多写少"支持,默认读取时不加锁
dirty map[any]*entry //修改的数据,当m.misses满足条件时,把m.dirty同步给m.read
misses int//计数器,表示m.dirty中有多少与read不同步的次数
}
//读取
func (m *Map) Load(key any) (value any, ok bool) {
//先从m.read获取key的值
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
if !ok && read.amended {//当m.read中不存在key且m.dirty有m.read中不存在的key时
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
e, ok = m.dirty[key]//从m.dirty中读取
m.missLocked()//如果满足同步条件,将m.dirty同步到m.read
}
m.mu.Unlock()
}
if !ok {
return nil, false
}
return e.load()
}
//存储
func (m *Map) Store(key, value any) {
//如果m.read中存在key,则更新value
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 {//若m.read中存在key,则更新
if e.unexpungeLocked() {//判断entry没有被标记expunged
m.dirty[key] = e
}
e.storeLocked(&value)
} else if e, ok := m.dirty[key]; ok {//若m.dirty中存在key,则更新
e.storeLocked(&value)
} else {//否则,将新建entry
if !read.amended {//若read于dirty数据一致,则在read中key
m.dirtyLocked()
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
其它方法:
func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool) //读取并更新key,当key存在时,返回原值,不存在是新建key
func (m *Map) LoadAndDelete(key any) (value any, loaded bool) //读取并删除key
func (m *Map) Delete(key any) //删除key
func (m *Map) Range(f func(key, value any) bool) //遍历m
func (m *Map) missLocked() //将m.dirty同步到m.read,并重置m.dirty
func (m *Map) dirtyLocked() //将m.read同步到m.dirty
使用m.read.amended 来标识read和dirty数据是否一样
使用m.misses 来决定什么时候进行数据同步.
我使用的Go版本是1.19