数据结构
sync.Map的实现由三个结构体,一个全局变量和若干方法函数组成。
核心数据结构Map:
type Map struct {
mu Mutex
// 存的是一个叫做readOnly的不可导出结构体,只读,并且由于是存在atomic.Value中,所以读写readOnly是并发安全的。
read atomic.Value
// 通过Store()方法存储的数据都会保存在这里,也就是所有新增的数据都保存在这里
dirty map[any]*entry
// 在read中找不到key的次数,如果misses>len(dirty),dirty中的数据会被保存到read中,然后将dirty置为nil
misses int
}
readOnly结构体:
type readOnly struct {
m map[interface{}]*entry // map数据结构
amended bool // 为true时表示dirty中包含了m中没有的key。为false时表示dirty为nil。
}
entry结构体:
type entry struct {
// p是一个指针类型,这说明map中保存的是value值的地址:
// value的值类型为结构体指针,保存的是指针的地址,如果值类型为结构体,保存的是结构体的地址
p unsafe.Pointer // *interface{}
}
全局变量:
var expunged = unsafe.Pointer(new(any)) // 被删除的value会被赋值为该值,标记该value已经被删除
实现逻辑
map类的数据结构本质上就两类操作,一类是读操作,一类是写操作。对于sync.Map来说,读操作主要是Load()方法,写操作主要是Store()方法。
读逻辑:在读取的时候先从read map中取,如果没有则去dirty map中取,同时判断读取穿透的次数,如果超过了dirty的长度,则将dirty赋值给read,同时将dirty置为nil。
如图所示:
写逻辑:先从read map中查找,有则更新;再从dirty map中查找,有则更新;如果都没有则说明需要走新增逻辑:如果dirty为nil,则先初始化dirty(将read中的未删除数据拷贝到dirty中),然后将新key添加到dirty中,并且将amended置为true。如图所示:
以上就是sync.Map大致的读写逻辑。
重要知识点:
sync.Map是一种基于读写分离和缓存思想设计的并发安全的Map,这种设计的好处是读快写慢。
sync.Map的正确用法:适用于读多写少的并发场景,不适用于写多读少的并发场景
原因是sync.Map的写操作(Store()方法)逻辑比较重,涉及到加锁,多个逻辑判断,以及map拷贝等操作,效率远低于直接对map加锁。
更多关于sync.Map的知识欢迎阅读我的另一篇文章: sync.Map源码详解