读和删除的性能更好,写的性能要差一些 参考 juejin.cn/post/701135… 为什么读和写的性能更好?有2点
- read 和dirty
- 乐观锁,cas
sync.map的结构
type Map {
mu Mutex
read atomic.Value
dirty map[interface{}]*entry
misses int
}
type entry struct {
p unsafe.Pointer
}
type readonly struct {
m map[interface{}]*entry
amened bool
}
entry.p 有三个值
- expunged, 表示这个key被删除了,在read里有,但是dirty里没有这个key
- nil 表示这个key被删除了,dirty 和 read里都有这个key
- 正常值
read 的类型实际是readonly, 要理解整个原理就先理解里面两个重要的方法 missLocked 和 dirtyLocked
当每次从read读,没读到,这时候有需要从dirty读,会调用missLocked 方法
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
}
misslock 方法会让misses++, 当dirty的长度小于 misses时会将dirty的数据给read, 自己置为nil, misses 重置为0.当然这时候amened = false 了也。
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
}
}
}
diryLock 方法是在什么时候调用呢?当missLock 触发后,diry 为nil,这时候来了一个新的key要插入,新的key在read里不存在,就会触发dirtyLocked。 触发如下:
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})
}
它会将read的数据拷贝给dirty,然后amended 设为true, 在遍历拷贝的时候,会尝试吧read 里entry.p 从nil改为expunge.且那些entry.p 的直expunged 不会被复制到dirty。这就表示了 expunged entry 不会出现在dirty里。
删除
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
if !ok && read.amended {
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
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()
}
if ok {
return e.delete()
}
return nil, false
}
删除就是先从read里找,如果能找得到就把entry.p 设为nil,return 如果read里找不到且amended=true, 表示diry里有一些read里没有的key,需要去dirty 找,这时候需要加锁,调用内置的delete(m.dirty,key) 删掉key,最后还要调用missLocked
store
func (m *Map) Store(key, value interface{}) {
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {
// 如果key对应的entity.p 不是expunged,就在read上设置值,ditry的map的值也会随之改变
return
}
m.mu.Lock()
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)
} else if e, ok := m.dirty[key]; ok {
e.storeLocked(&value)
} 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()
}
func (e *entry) unexpungeLocked() (wasExpunged bool) {
return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}
最后提一点:用*entry,p 存实际的值,有个好处是,read和dirty共享entry,修改了其中一个entry,另一个的值也就跟着变了