再来看一些子函数:
// 如果 entry 没被删,tryStore 存储值到 entry 中。如果 p == expunged,即 entry 被删,那么返回 false。
func (e *entry) tryStore(i *interface{}) bool {
for {
p := atomic.LoadPointer(&e.p)
if p == expunged {
return false
}
if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
return true
}
}
}
tryStore 在 Store 函数最开始的时候就会调用,是比较常见的 for 循环加 CAS 操作,尝试更新 entry,让 p 指向新的值。
unexpungeLocked 函数确保了 entry 没有被标记成已被清除:
// unexpungeLocked 函数确保了 entry 没有被标记成已被清除。
// 如果 entry 先前被清除过了,那么在 mutex 解锁之前,它一定要被加入到 dirty map 中
func (e *entry) unexpungeLocked() (wasExpunged bool) {
return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}
Load
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// 如果没在 read 中找到,并且 amended 为 true,即 dirty 中存在 read 中没有的 key
if !ok && read.amended {
m.mu.Lock() // dirty map 不是线程安全的,所以需要加上互斥锁
// double check。避免在上锁的过程中 dirty map 提升为 read map。
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
// 仍然没有在 read 中找到这个 key,并且 amended 为 true
if !ok && read.amended {
e, ok = m.dirty[key] // 从 dirty 中找
// 不管 dirty 中有没有找到,都要"记一笔",因为在 dirty 提升为 read 之前,都会进入这条路径
m.missLocked()
}
m.mu.Unlock()
}
if !ok { // 如果没找到,返回空,false
return nil, false
}
return e.load()
}
处理路径分为 fast path 和 slow path,整体流程如下:
首先是 fast path,直接在 read 中找,如果找到了直接调用 entry 的 load 方法,取出其中的值。 如果 read 中没有这个 key,且 amended 为 fase,说明 dirty 为空,那直接返回 空和 false。 如果 read 中没有这个 key,且 amended 为 true,说明 dirty 中可能存在我们要找的 key。当然要先上锁,再尝试去 dirty 中查找。在这之前,仍然有一个 double check 的操作。若还是没有在 read 中找到,那么就从 dirty 中找。不管 dirty 中有没有找到,都要"记一笔",因为在 dirty 被提升为 read 之前,都会进入这条路径这里主要看下 missLocked 的函数的实现:
func (m *Map) missLocked() {
m.misses++
if m.misses < len(m.dirty) {
return
}
// dirty map 晋升
m.read.Store(readOnly{m: m.dirty})
m.dirty = nil
m.misses = 0
}
直接将 misses 的值加 1,表示一次未命中,如果 misses 值小于 m.dirty 的长度,就直接返回。否则,将 m.dirty 晋升为 read,并清空 dirty,清空 misses 计数值。这样,之前一段时间新加入的 key 都会进入到 read 中,从而能够提升 read 的命中率。










