sync.Map是我比较喜欢的一个库,用了非常久,今天突发奇想瞧瞧它的实现。又一次被宇宙中第二NB的语言--go 折服了。 这里准备写一篇文章,讨论下当使用sync.Map执行操作的时候,会发生什么。
map结构
代码很简单,sync/map.go中一百多行。总体讲一讲Load, Store, Delete三个接口发生了什么。 首先是sync.Map的结构:
代码语言:javascript复制sync.Map {
read ,一个真实的map ]
amended, bool变量;
dirty , 一个真实的map
misses , int 记录读取的时候,在read map中miss的次数
}
操作一下
Store key:1
此时会初始化dirty map,初始化read map,并把amended设为true,这个key存到dirty map中。这里加锁。
执行的代码如下:
代码语言:javascript复制 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)
Store key 2,3
此时直接存dirty map,上面的if !read.amended
不会执行了。这里加锁。
只有store 已存在的key(修改操作),可以无锁执行。使用的是atomic.Value结构的功能。
Load key 1
load命令首先会从read map查,如果查不到,amended又是true,那就尝试从dirty map中查,并且记miss。
再Load key:1 两次
当miss的数量等于dirty map的长度的时候,dirty map将直接升级为read map。并且dirty map置为nil。这里需要加锁。
参考代码:
代码语言:javascript复制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
}
store key: 4
存入一个不存在的key,非常有意思的事情会发生。 此时,dirty map还是nil,它会进行初始化。将read map 拷贝一份过来。然后,将新值存在dirty map,并标记read map amended 为 true。这里加锁。
参考代码:
代码语言:javascript复制 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() // 就是在这里初始化dirty map
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
删除 key: 4
因为key:4 不在read中,在dirty map中且amended为true。所以,直接在dirty map中把key:4 删除。这里加锁。
删除 key: 3
key 3在read map中,直接将key:3 指向nil,注意不是(expunge)。这里无锁。
Store key:3 val: 1234
store一个已存在的key。 如果在read map中,直接修改val为1234。 这里值得一提的是,无论是read map还是dirty map,同一个key,指向的是一个val。这里无锁。
Delete key:3; Store key: 4 ; Load key:4 4次;Store key:5
Delete key:3, 此时,key:3 指向nil。这里无锁。
store key 4; Load key:4 4次。按照上面的情况分析,此时,dirty map被升级为read map,dirty map=nil 。
此时再store key:5, amended 被标记为true,dirty 复制read的数据。 在复制过程中,会判定 3的值是不是nil,如果是,则将值设置为 expunged。并且,不再复制到dirty。 如果一直没有人再执行Store key:3 。在下次dirty 升级的时候,key:3 就会被丢弃。
参考代码:
代码语言:javascript复制func (e *entry) tryExpungeLocked() (isExpunged bool) {
p := atomic.LoadPointer(&e.p)
for p == nil {
if atomic.CompareAndSwapPointer(&e.p, nil, expunged) { // 这个原子操作将nil的值改为 expunged值。return true使这个key不会被添加到dirty
return true
}
p = atomic.LoadPointer(&e.p)
}
return p == expunged
}
Store key:3 val: 1234
如果在read map中,key:3 存在,且被标记为删除(expunged),那么,把这个key添加到dirty map中。并修改值为1234。这里有锁。
参考代码:
代码语言:javascript复制 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
}
为什么read中存在值为expunged的key时,这个时候dirty map一定不为nil呢。
1. 因为 expunged 的设置命令出现在dirtyLocked -> tryExpungeLocked
这个调用的原子操作中(详细见上面一节),执行时,dirty 已经存在。所以,如果read中有值为 expunged 的key。那一定在dirtyLocked
执行之后。
2. 因为dirty 中不可能存在值为expunged的key。dirty如果升级,read中一定不会有值为 expunged 的key。