为什么需要Sync.Map
首先,map在并发场景下并不是thread free的,所以在操作map的时候需要加锁,相比于加锁,原子操作自然是更节省时间的,所以Go的作者们就引入了Sync.Map
代码示例
package main
import (
"fmt"
"sync"
)
func main() {
var syncMap sync.Map
var wg sync.WaitGroup
wg.Add(2)
go func() {
wg.Done()
syncMap.Store("1", 1)
}()
go func() {
wg.Done()
syncMap.Store("2", 2)
}()
wg.Wait()
syncMap.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true
})
}
在上面的例子中,同时开启两个goroutine对sync.Map进行操作,并未出现并发异常,且最终输出了正确的结果
源码剖析
在看核心代码之前,先来看一下相关结构体的定义
type Map struct {
mu Mutex // 操作dirty map时需要加锁
read atomic.Value // 只读map,这里通过更改read中指针的状态来进行逻辑删除
// dirty map相当于一个兜底,它里面的key/value对会比read map中要全
dirty map[interface{}]*entry
misses int // 当读取数据的时候,会先从read map中去读,如果没有的话,再去dirty map中查找,此时misses数加1
}
// read对应的存储数据
type readOnly struct {
m map[interface{}]*entry
amended bool // 如果dirty map比read map中存储的数据要全,则该字段的值为true
}
// expunged表示当前的key已经从dirty map中删除,也就是read map中有而dirty map中没有
var expunged = unsafe.Pointer(new(interface{}))
// entry结构体中包含着一个指向map中value的指针
type entry struct {
// p points to the interface{} value stored for the entry.
//
// If p == nil, the entry has been deleted and m.dirty == nil.
//
// If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
// is missing from m.dirty.
//
// Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty
// != nil, in m.dirty[key].
//
// An entry can be deleted by atomic replacement with nil: when m.dirty is
// next created, it will atomically replace nil with expunged and leave
// m.dirty[key] unset.
//
// An entry's associated value can be updated by atomic replacement, provided
// p != expunged. If p == expunged, an entry's associated value can be updated
// only after first setting m.dirty[key] = e so that lookups using the dirty
// map find the entry.
p unsafe.Pointer // *interface{}
}
接下来我们分析Sync.Map中的三个核心操作: Store/Load/Delete
Store(存储)
func (m *Map) Store(key, value interface{}) {
// 首先会从read map中进行读取,如果某个键存在并且值的状态不为expunged的话,存储成功
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
// 否则需要对dirty map进行操作,由于dirty是一个普通的map,所以这里需要加锁
m.mu.Lock()
// 如果当前的键在read map中是存在的,并且状态为expunged的话,则说明dirty map != nil
// && dirty map中不存在该键值对,首先将状态修改为nil,然后放入dirty map中,之后
// 进行赋值操作
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {
// The entry was previously expunged, which implies that there is a
// non-nil dirty map and this entry is not in it.
m.dirty[key] = e
}
e.storeLocked(&value)
// 如果当前的键在dirty map中存在,直接存储
} else if e, ok := m.dirty[key]; ok {
e.storeLocked(&value)
} else {
// 如果当前的键在dirty map以及read map中都不存在,如果dirty map为nil,则将read map
// 中所有不为nil和expunged状态的键值对给dirty map,之后将新的键值对放入dirty map中
if !read.amended {
// We're adding the first new key to the dirty map.
// Make sure it is allocated and mark the read-only map as incomplete.
m.dirtyLocked()
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
// 在read map中尝试存储,如果entry的状态为expunged的话,则返回false,否则进行CAS操作
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
}
}
}
// 将expunged状态替换为nil
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))
}
// 将read map中所有状态不为nil以及expunged的键值对放入dirty map中
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
}
Load(加载)
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
// 首先先从read map中找数据
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// 如果不存在,并且dirty map不为空
if !ok && read.amended {
m.mu.Lock()
// Avoid reporting a spurious miss if m.dirty got promoted while we were
// blocked on m.mu. (If further loads of the same key will not miss, it's
// not worth copying the dirty map for this key.)
// 进行double check,防止在加锁的过程中read map中有了该key
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
// 如果依旧没有,则从dirty map中取数据,并计数miss次数
e, ok = m.dirty[key]
// Regardless of whether the entry was present, record a miss: this key
// will take the slow path until the dirty map is promoted to the read
// map.
m.missLocked()
}
m.mu.Unlock()
}
// 如果没有找到,返回nil
if !ok {
return nil, false
}
return e.load()
}
// 计数没有命中read map的次数,如果命中次数达到dirty map的长度,则将dirty map中的值统一给read map
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
}
// 加载value值
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
}
Delete(删除)
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
// 删除操作和加载操作很类似,首先从read map中找对应的key
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// 如果没有并且dirty map不为空,则从dirty map中找
if !ok && read.amended {
m.mu.Lock()
// 进行double check,防止在加锁的过程中添加了该key
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
// 如果依旧没有,则从dirty map中找,并进行删除,同时增加miss的计数值
if !ok && read.amended {
e, ok = m.dirty[key]
delete(m.dirty, key)
// Regardless of whether the entry was present, record a miss: this key
// will take the slow path until the dirty map is promoted to the read
// map.
m.missLocked()
}
m.mu.Unlock()
}
// 将该值的状态置为nil,表示已删除
if ok {
return e.delete()
}
return nil, false
}
// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
m.LoadAndDelete(key)
}
// 删除entry
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
}
}
}
看完源代码,可以发现,如果写操作很多的话,如果read map中的值不能全部命中,会进行加锁且dirty map会拷贝read map中的键值对,性能方面会大打折扣,所以Sync.Map适用于读多写少的情况