Golang sync map

sync.Map 是go语言标准库实现的并发安全的map;

0x01 使用场景

下面的两个场景可以减少锁使用

  1. 读多写少场景
  2. 多个协程读写,覆盖key不想交的场景(key不重合)

0x02 代码实现

代码的设计逻辑为双map,分为read Map 和dirty Map。两个map协作,对外提供CRUD功能的时候会出现下面的情况,有一个有,一个没有,或者双缺的情况。那么出现dirty和read 都有对应key的情况下谁的数据为准呢?下面具体看下代码实现。

//只读map定义
type readOnly struct {
	m       map[interface{}]*entry
  //dirty中有,但是read中没有的情况。
	amended bool // true if the dirty map contains some key not in m.
}
type Map struct {
 //互斥锁
	mu Mutex
	//对象为上面定义的readOnly 结构体
	read atomic.Value // readOnly
	//脏map
	dirty map[interface{}]*entry
	//缺失数
	misses int
}
//read 场景
//load key 逻辑
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
	//1. 先去read中读取,read中有,直接返回,不需要去dirty取
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
	if !ok && read.amended {
		m.mu.Lock()
		//read没有,并且read中缺失了新写入的key
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
			//去dirty中读
			e, ok = m.dirty[key]
			//下面的函数记录一次miss,但是当miss的次数
			//赶上dirty的key的数量的时候,直接将dirty的map
			//切到read中
			m.missLocked()
		}
		m.mu.Unlock()
	}
	//read,dirty双缺的情况下返回false
	if !ok {
		return nil, false
	}
	//返回具体值
	return e.load()
}
//insert/update key 逻辑
// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
  //read 有 , dirty没有场景
	//1.先去read map读取值,如果找到了,直接更新,这里的更新(tryStore)
	//用了原子操作
	read, _ := m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok && e.tryStore(&value) {
		return
	}
	//2. 没有找到,上锁,如果在临界区中在read中找到key
	//那么更新key的值,并更新dirty的中值
	m.mu.Lock()
	//read有dirty没有场景
	read, _ = m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok {
		if e.unexpungeLocked() {
			m.dirty[key] = e
		}
		e.storeLocked(&value)
	
	//在read中没有找到,在dirty中找到了,更新dirty中的值
	} else if e, ok := m.dirty[key]; ok {//dirty有,read没有场景
		e.storeLocked(&value)
	} else {//双缺场景
		//在read和dirty map中都没有找到的情况想,如果此刻read中的缺失标记
		//为false,那么将read中的缺失标记设置为true,并将read中的值拷贝到dirty中。
		if !read.amended {
		//下面这行代码将read的中的值copy到dirty中,这里为什么要这样做呢,
		//因为在这个函数的开头部分,可以看到read中的已有key是可以直接被更新的
		//这里就需要dirty 和 read保持一致,并让dirty 以read的值为准
			m.dirtyLocked()
			//更新read的值,将缺失设置为true,因为,read和dirty 双缺的情况下
			//第一次只在dirty中加入了新key。
			m.read.Store(readOnly{m: read.m, amended: true})
		}
		//赋值dirty map
		m.dirty[key] = newEntry(value)
	}
	m.mu.Unlock()
}

//delete key
// LoadAndDelete deletes the value for a key, returning the previous value if any.
// The loaded result reports whether the key was present.
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
//去read中读
	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 {
			//在dirty找到,在read中没有;清理掉dirty中的key
			e, ok = m.dirty[key]
			delete(m.dirty, key)
			//记录一次miss
			m.missLocked()
		}
		m.mu.Unlock()
	}

	if ok {
		return e.delete()
	}
  //双缺
	return nil, false
}

0x03 总结

  1. read 和dirty map,以read map中的记录为准
  2. dirty map中的数据在miss次数大于dirty的大小的时候,dirty map数据会直接更新到read中
  3. 插入新key的时候并不会立即更新read,而会插入dirty map,并记录read map有缺失和dirty不一致
  4. 在插入新值的时候,由于不会立即插入到read中,但是会以read数据为准,所以插入dirty前会将read数据copy到dirty中,使两者在变化前保持一致。
  5. read 中的amended 变量只有在插入数据的时候才会设置为true,当删除read 中的key,没有删除dirty的中的key时,再读的时候即使在read中miss也不会去dirty中读。