// sync.map底层数据结构
type Map struct {
mu Mutex
read atomic.Value
dirty map[interface{}]*entry
misses int
}
// read数据结构
type readOnly struct {
m map[interface{}]*entry
amended bool // true if the dirty map contains some key not in m.
}
// map中value的载体
type entry struct {
p unsafe.Pointer // *interface{}
}
var expunged = unsafe.Pointer(new(interface{}))
sync.Map底层是Map结构体,包含四个元素:
mu
: 在更新read
和dirty
时使用并发锁read
: 是一个只读readOnly
结构,sync.Map的主要读操作从read
获取dirty
: 一个map
,当数据变更时,优先变更dirty
结构,并在适当的时机同步read
misses
: 记录从read中取不到数据的次数(决定是否需要将dirty
复制给read
); read
每次更新后置0read
底层是一个atomic.Value
, 存储readOnly
结构体
m
: read
里存储数据的mapamended
: 布尔值,标记dirty map
是否有元素没同步到read.m
sync.map
中value
值是放在entry
结构体中存储的
p
记录value的指针expunged
: 一个特殊的变量,标记dirty map
中已经被删除的entry
sync.atomic.Value
atomic.Value
底层是一个包含interface{}
的结构体
type Value struct {
v interface{}
}
atomic.Value
提供原子读写的能力;主要支持对int32 int64 uint32 uint64 uintptr unsafe.Pointer
的add swap compareAndSwap
操作
sync/atomic/doc.go
对这些操作做了简要介绍,功能和函数名差不多;官方不建议应用层直接使用atomic.Value
,推荐使用channel
或者sync包中的其他工具进行数据同步
func (m *Map) Store(key, value interface{}) {
read, _ := m.read.Load().(readOnly)
// 如果key在read.m中已存在,且对应entry没有被标记删除,直接更新entry的指针
// 此时相当于read.m和dirty同时更新了
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
// read.m中存在这个key,但被标记删除:
// 1. 去除entry"被删除标记", entry.p, expunged => nil
// 2. 将entry赋值给dirty
// 3. 将value值写入entry
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {
m.dirty[key] = e
}
e.storeLocked(&value)
// read.m中没有这个key,但dirty中有这个key
// 1. 将value写给dirty中的entry
} else if e, ok := m.dirty[key]; ok {
e.storeLocked(&value)
// read.m和dirty中都没有这个key
// 1. 新建一个entry, 赋值value,并写入dirty
// 2. 将read.amended设置为true, 表示read和dirty数据已经不一致了(这种情况没有新增到read.m)
} else {
if !read.amended {
m.dirtyLocked()
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
// 尝试将i对应值写入entry
func (e *entry) tryStore(i *interface{}) bool {
for {
p := atomic.LoadPointer(&e.p)
// 如果entry被标记删除,返回写入false
if p == expunged {
return false
}
// 否则,将i的指针更新到entry.p
if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
return true
}
}
}
// 将i值写给entry.p
func (e *entry) storeLocked(i *interface{}) {
atomic.StorePointer(&e.p, unsafe.Pointer(i))
}
Store
操作总结:
key
,会直接更新对应entry
的指针指向
read.m
和 dirty
,不会造成read
和dirty
数据不一致key
,会优先写进dirty
read.m
和dirty
数据不一致func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
read, _ := m.read.Load().(readOnly)
// 先直接从read.m中取,如果已存在,直接返回
e, ok := read.m[key]
// read.m中没有,并且amended = true(表示dirty和read数据不一致,dirty可能有)
// 1. 加锁。这一步加锁后重新从read.m中取,是防止加锁期间read.m被其他协程更新
// 2. 尝试从dirty中获取
// 3. 从read中miss的次数加一,并判断是否需要重建read
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()
}
// missLocked即增加一次从read中取不到的次数
// 如果misses的次数比dirty的长度还大,则将dirty重写到read,misses 置零
func (m *Map) missLocked() {
m.misses++
if m.misses < len(m.dirty) {
return
}
m.read.Store(readOnly{m: m.dirty})
m.dirty = nil // ??? 为啥把dirty改成nil???
m.misses = 0
}
Load
操作总结:
Store
中的update
互相验证
misses
计数器记录从read.m
中读取失败的次数,达到阈值,需要重建read.m
func (m *Map) Delete(key interface{}) {
m.LoadAndDelete(key)
}
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// read.m存在这个key,直接将entry.p 设置成nil
// read.m不存在,且read和dirty数据不一致
// 1. 从dirty中删除这个key
// 2. misses计数 +1
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]
delete(m.dirty, key)
m.missLocked()
}
m.mu.Unlock()
}
if ok {
return e.delete()
}
return nil, false
}
// entry的delete操作
// 如果entry.p 已经是nil,或者被标记删除,直接返回
// 否则,将entry.p 设置成nil
func (e *entry) delete() (value interface{}, ok bool) {
for {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return nil, false
}
if atomic.CompareAndSwapPointer(&e.p, p, nil) {
return *(*interface{})(p), true
}
}
}
Delete
操作总结:
read.m
里有,直接将entry.p
设置为nil (dirty共用一个entry指针,dirty里数据也为nil)read.m
里没有,从dirty中删除delete
不会从read
中直接删除keyfunc (m *Map) Range(f func(key, value interface{}) bool) {
read, _ := m.read.Load().(readOnly)
// 如果read和dirty数据不一致,直接遍历dirty了
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
的使用场景:
// 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.
missLocked()
函数中,如果达到阈值会将dirty
拷贝给read.m
, 为什么要把dirty设置为nil?如此还怎么保证dirty
的数据一定包含read.m
呢?