sync.map

144 阅读2分钟

读和删除的性能更好,写的性能要差一些 参考 juejin.cn/post/701135… 为什么读和写的性能更好?有2点

  1. read 和dirty
  2. 乐观锁,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,另一个的值也就跟着变了