sync.Map
Go语言中内置的map不是并发安全的
sync.Map 有以下特性:
无须初始化,直接声明即可。
sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
底层原理
type Map struct {
mu Mutex
// 后面是readOnly结构体,依靠map实现,仅仅只用来读
read atomic.Value // readOnly
// 这个map主要用来写的,部分时候也承担读的能力
dirty map[interface{}]*entry
// 记录自从上次更新了read之后,从read读取key失败的次数,来触发dirty更新到read操作
misses int
}
type readOnly struct {
m map[interface{}]*entry
// dirty是否包含m中不存在的key
amended bool // true if the dirty map contains some key not in m.
}
type entry struct {
p unsafe.Pointer // *interface{} 执行存储值得地址
}
// 后面是readOnly结构体,依靠map实现,仅仅只用来读
read atomic.Value // readOnly
// 这个map主要用来写的,部分时候也承担读的能力
dirty map[interface{}]*entry
// 记录自从上次更新了read之后,从read读取key失败的次数,来触发dirty更新到read操作
misses int
}
type readOnly struct {
m map[interface{}]*entry
// dirty是否包含m中不存在的key
amended bool // true if the dirty map contains some key not in m.
}
type entry struct {
p unsafe.Pointer // *interface{} 执行存储值得地址
}</pre>
使用了两个map,一个叫read,一个叫dirty,两个map存储的都是指针,指向value数据本身,所以两个map是共享value数据的,更新value对两个map同时可见。
read map 使用 atomic.Value,保证读写操作的原子性
dirty map 使用 mutex
来保护,进行新增一个key进行存储
结构示意图
通过上面的结构体,我们可以简单画出来一个结构示意图
Store操作
func (m *Map) Store(key, value interface{}) {
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
// 走到这里有两种情况,
// 1. key不存在
// 2. key对应的值被标记为expunged,read中的entry拷贝到dirty时,会将key标记为expunged,需要手动解锁
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
// read map存在 但是expunged,就先拷贝到dirty map,存储value
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)
// read map 不存在, dirty map 存在,直接存value
} else if e, ok := m.dirty[key]; ok {
e.storeLocked(&value)
// read map 不存在, dirty map 也不存在,在dirty新建一个entry存储,并关联read map
} else {
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()
}
key原先就存在于read中,获取key所对应内存地址,原子性修改
key存在,但是key所对应的值被标记为 expunged,解锁,解除标记,并更新dirty中的key,与read中进行同步,然后修改key对应的值
read中没有key,但是dirty中存在这个key,直接修改dirty中key的值
read和dirty中都没有值,先判断自从read上次同步dirty的内容后有没有再修改过dirty的内容,没有的话,先同步read和dirty的值,然后添加新的key value到dirty上面
Load操作
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// 如果read的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.)
// 再查找一次,有可能刚刚将dirty升级为read了
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
// 如果amended 还是处于修改状态,则去dirty中查找
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.
// 增加misses的计数,在计数达到一定规则的时候,触发升级dirty为read
m.missLocked()
}
m.mu.Unlock()
}
// read dirty中都没有找到
if !ok {
return nil, false
}
// 找到了,通过load判断具体返回内容
return e.load()
}
func (e *entry) load() (value interface{}, ok bool) {
p := atomic.LoadPointer(&e.p)
// 如果p为nil或者expunged标识,则key不存在
if p == nil || p == expunged {
return nil, false
}
return *(*interface{})(p), true
}
-
read map 没找到,但是为amended 修改状态
2.再读一次,看是否dirty更新到read,去read 读
3.还是 不存在,修改状态
就去dirty map中读,并增加missed计数,来触发dirty更新到read中操作
找不到返回