sync.Map
sync.Map 是1.9版本才加入的,是线程并发安全的map,类型可以看做map[interface{}]interface{}。
结构
Map
type Map struct {
mu Mutex //互斥锁,用于锁定dirty map
read atomic.Value //优先读map,支持原子操作,源码注释写了readOnly不是说read是只读,而是它的结构体是readOnly
dirty map[interface{}]*entry // dirty是一个当前最新的map,允许读写
misses int // 主要记录read读取不到数据加锁读取read map以及dirty map的次数,当misses等于dirty的长度时,会将dirty复制到read
}
readOnly
type readOnly struct {
m map[interface{}]*entry
amended bool // 如果数据在dirty中但没有在read中,该值为true,作为修改标识
}
entry
type entry struct {
// nil: 表示为被删除,调用Delete()可以将read map中的元素置为nil
// expunged: 也是表示被删除,但是该键只在read而没有在dirty中,这种情况出现在将read复制到dirty中,即复制的过程会先将nil标记为expunged,然后不将其复制到dirty
// 其他: 表示存着真正的数据
p unsafe.Pointer // *interface{}
}
Load 查找
根据key来查找 value, 函数为 Load(),源码如下:
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
// 首先从只读ready的map中查找,这时不需要加锁
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// 如果没有找到,并且read.amended为true,说明dirty中有新数据,从dirty中查找,开始加锁了
if !ok && read.amended {
m.mu.Lock() // 加锁
// 又在 readonly 中检查一遍,因为在加锁的时候 dirty 的数据可能已经迁移到了read中
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
// read 还没有找到,并且dirty中有数据
if !ok && read.amended {
e, ok = m.dirty[key] //从 dirty 中查找数据
// 不管m.dirty中存不存在,都将misses + 1
// missLocked() 中满足条件后就会把m.dirty中数据迁移到m.read中
m.missLocked()
}
m.mu.Unlock()
}
if !ok {
return nil, false
}
return e.load()
}
从函数可以看出,如果查询的键值正好在m.read中,不需要加锁,直接返回结果,优化了性能。
即使不在read中,经过几次miss后, m.dirty中的数据也会迁移到m.read中,这时又可以从read中查找。所以对于更新/增加较少,加载存在的key很多的case,性能基本和无锁的map类似。
missLockerd 迁移数据
func (m *Map) missLocked() {
m.misses++
if m.misses < len(m.dirty) {//misses次数小于 dirty的长度,就不迁移数据,直接返回
return
}
m.read.Store(readOnly{m: m.dirty}) //开始迁移数据
m.dirty = nil //迁移完dirty就赋值为nil
m.misses = 0 //迁移完 misses归0
}
Store 存储
func (m *Map) Store(key, value interface{}) {
// 直接在read中查找值,找到了,就尝试 tryStore() 更新值
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
// m.read 中不存在
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() { // 未被标记成删除,前面讲到entry数据结构时,里面的p值有3种。1.nil 2.expunged,这个值含义有点复杂,可以看看前面entry数据结构 3.正常值
m.dirty[key] = e // 加入到dirty里
}
e.storeLocked(&value) // 更新值
} else if e, ok := m.dirty[key]; ok { // 存在于 dirty 中,直接更新
e.storeLocked(&value)
} else { // 新的值
if !read.amended { // m.dirty 中没有新数据,增加到 m.dirty 中
m.dirtyLocked() // 从 m.read中复制未删除的数据
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value) //将这个entry加入到m.dirty中
}
m.mu.Unlock()
}
操作都是先从m.read开始,不满足条件再加锁,然后操作m.dirty。
Delete 删除
func (m *Map) Delete(key interface{}) {
// 从 m.read 中开始查找
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
if !ok && read.amended { // m.read中没有找到,并且可能存在于m.dirty中,加锁查找
m.mu.Lock() // 加锁
read, _ = m.read.Load().(readOnly) // 再在m.read中查找一次
e, ok = read.m[key]
if !ok && read.amended { //m.read中又没找到,amended标志位true,说明在m.dirty中
delete(m.dirty, key) // 删除
}
m.mu.Unlock()
}
if ok { // 在 m.ready 中就直接删除
e.delete()
}
}
使用
package main
import (
"fmt"
"sync"
)
func main() {
var sm sync.Map
sm.Store("www", "2343125")
sm.Store("test", 111)
var v interface{}
v, _ = sm.Load("www")
fmt.Printf("%T", v)
sm.Delete("test")
v, _ = sm.Load("test")
fmt.Printf("%T", v)
}
// string<nil>
参考
sync.Map
https://blog.puresai.com/2021/02/06/syncMap/