阅读 39

golang sync.Map 源码剖析

sync.Map的数据结构如下

type Map struct{
mu Mutext    //dirty操作的时候需要使用这个锁
read atomic.Value //只读的数据
dirty map[interface{}]*entry//新写的值都放在dirty里,和read有冗余的嫌疑
misses int//read没命中值的次数
}
复制代码

read的数据结构是

type readOnly struct{
m map[interface{}]*entry
amended bool
}
复制代码

entry的数据结构 里面是一个指针,指向用户存储的value值

type entry struct{
p unsafe.Pointer
}
复制代码

虽然read 和 dirty 有冗余数据,但这readOnly.m和Map.dirty存储的值类型是*entry,是一个指针,指向同一个数据,所以尽管map的value很大,冗余的空间占用还是有限的(疑问:直接指向value的指针不就可以了,为什么还要结构体再包一层,里面套个指针)

使用方法有 load 源码如下

// Load returns the value stored in the map for a key, or nil if no
// value is present.
// The ok result indicates whether value was found in the map.
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
	if !ok && read.amended {//如果read里面没有,并且dirty和read数据不一致,可能有
		m.mu.Lock()//加锁,去dirty里面寻找
		read, _ = m.read.Load().(readOnly)//双检查,再次看read里面有没有,因为加锁期间可能read数据有变化
		e, ok = read.m[key]
		if !ok && read.amended {//read没有,dirty可能有
			e, ok = m.dirty[key]
			m.missLocked()//这是一个额外操作,和取值无关,因为read miss了一次,所以这个方法miss++ ,到一定程度,迁移dirty数据到read上,具体看下面方法详解
		}
		m.mu.Unlock()//取完解锁
	}
	if !ok {//有几种情况 1read没取到,dirty没有数据,只查看了read 2read没取到,dirty数据不一致(amended为true)查了dirty也没取到
		return nil, false
	}
	return e.load()//取到,还要看具体value的情况返回,具体看下面发放解释
}

复制代码

missLocked的源码如下,主要是miss如果过多了,说明每次都是先找read找不到再去dirty寻找,这样找两次,而且还加锁,效率很低,所以需要把dirty的数据给read,这样每次可以直接read找到返回

func (m *Map) missLocked() {
	m.misses++//去dirty找一次,miss加1
	if m.misses < len(m.dirty) {//miss的个数还没有达到dirty中元素的个数,直接返回
		return
	}
	m.read.Store(readOnly{m: m.dirty})//miss已经过多,read存储dirty中的数据
	m.dirty = nil//dirty清空
	m.misses = 0//miss清空
}
复制代码

*entry 的load方法源码如下

func (e *entry) load() (value interface{}, ok bool) {
	p := atomic.LoadPointer(&e.p)//去除指针
	if p == nil || p == expunged {//虽然可以找到key对应的value的地址,但是地址内的数据已经删除了
		return nil, false//返回false表示没有找到
	}
	return *(*interface{})(p), true//返回找到并且返回具体值
}
复制代码

store方法的源码如下

// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
	read, _ := m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok && e.tryStore(&value) {//如果read里面有并且没有删除,更新value值
		return
	}

	m.mu.Lock()//到这里有两种情况 1read里面没有 2read里面有但是已经删除
	read, _ = m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok {//第2种情况 read有但已经删除
		if e.unexpungeLocked() {//如果去除删除标识成功
			m.dirty[key] = e//赋值给dirty
		}
		e.storeLocked(&value)//把value赋值给e
	} else if e, ok := m.dirty[key]; ok {//read里面没有值,dirty里面有值
		e.storeLocked(&value)//更新value值
	} else {//read和dirty里面都没有值
		if !read.amended {//如果read和dirty数据一致
			m.dirtyLocked()//如果dirty为空的时候把read里面没有删除的数据复制给dirty,不为空就什么都不做
			m.read.Store(readOnly{m: read.m, amended: true})
		}
		m.dirty[key] = newEntry(value)//把新的key value放到dirty里面
	}
	m.mu.Unlock()
}
复制代码

总结一下就是先看read,1.如果有就直接更新相应entry里的值;2.如果read删除了,就去除删除标记,更新entry对应的值;3.如果read没有,dirty有,更新相应entry对应的值;4.如果都没有,直接在dirty新增,大致这样,当然还有一些细节
Store可能会在某种情况下(初始化或者m.dirty刚被提升后)从m.read中复制数据,如果这个时候m.read中数据量非常大,可能会影响性能,这部分逻辑在dirtyLocked方法里面
storeLocked的源码,就是把value值放在entry结构体中

// The entry must be known not to be expunged.
func (e *entry) storeLocked(i *interface{}) {
	atomic.StorePointer(&e.p, unsafe.Pointer(i))
}
复制代码
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//复制给dirty
		}
	}
}
复制代码

delete 的源码

// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
	if !ok && read.amended {//如果read没有但是dirty可能有
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {//双检查 如果read还是没有 dirty可能有
			delete(m.dirty, key)//删除dirty里面的值,这个应该是硬删除,从这里可以看出如果删除一个不存在的值也不会报错,直接返回的
		}
		m.mu.Unlock()
	}
	if ok {//read里面有值
		e.delete()//软删除,将这个值对应的entry更新为nil
	}
}
复制代码

range遍历源码如下

func (m *Map) Range(f func(key, value interface{}) bool) {
	read, _ := m.read.Load().(readOnly)
	if read.amended {//如果dirty有新数据
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		if read.amended {//双检查
			read = readOnly{m: m.dirty}//把dirty 的值给read
			m.read.Store(read)
			m.dirty = nil//清空dirty
			m.misses = 0
		}
		m.mu.Unlock()
	}

	for k, e := range read.m {
		v, ok := e.load()
		if !ok {
			continue
		}
		if !f(k, v) {
			break
		}
	}
}
复制代码

sync.Map没有Len方法,并且目前没有迹象要加上,所以如果想得到当前Map中有效的entries的数量,需要使用Range方法遍历一次

文章分类
后端
文章标签