usage
package mainimport ("sync""fmt")func main() {//开箱即用var sm sync.Map//store 方法,添加元素sm.Store(1,"a")//Load 方法,获得valueif v,ok:=sm.Load(1);ok{fmt.Println(v)}//LoadOrStore方法,获取或者保存//参数是一对key:value,如果该key存在且没有被标记删除则返回原先的value(不更新)和true//不存在则store,返回该value 和falseif vv,ok:=sm.LoadOrStore(1,"c");ok{fmt.Println(vv)}if vv,ok:=sm.LoadOrStore(2,"c");!ok{fmt.Println(vv)}//遍历该map,参数是个函数,该函数参的两个参数是遍历获得的key和value,返回一个bool值,//当返回false时,遍历立刻结束。sm.Range(func(k,v interface{})bool{fmt.Printf("%v:%v \n",k,v)return true})}
简介
- sync.Map 和普通的 map[interface{}]interfae{} 使用起来很像,但是保证了线程安全。
- sync.Map 是某些场景下专用的,比如:
- 一个 key 只写入一次,但是需要被多次读取。(读多写少)
- 多个 goroutine 同时读取、写入和覆盖一系列不想交的 key 时。
在以上两类情况下,和单独加读写锁的内置 map 相比,sync.Map 可以有效的减少锁竞争。
设计思想
通过冗余的两个 map,牺牲空间换时间。
- 两个 map 的 value 类型都为 *interface{},所以可以同时进行修改。
- read map 是原子类型的,自动更新,用于读操作,无需加锁;dirty map 含有最新数据并且所有操作都要使用 mutex。
- 读取数据首先在 read 上读,如果没读到就去 dirty 上读,记录一个未命中数,当未命中数到达一定数量时,使用 dirty 的数据更新 read。
为什么 read map 是原子类型的?
因为如果并发的
稍加思索可以得出这样设计的好处是:
- 读操作无需获取和释放锁;
-
实现
Map
type Map struct {mu Mutexread atomic.Value // 只读类型,自动更新// dirty 的使用需要加锁dirty map[interface{}]*entry// 从 Map 读取 entry 时,如果 read 未命中将会从 dirty 中读取,此时 misses++// 当 misses 等于 dirty 的长度时,避免命中率过低,dirty 将升级为 readmisses int}
readOnly
// readOnly 自动存储 dirty map 更新的数据,不能手动改变type readOnly struct {m map[interface{}]*entryamended bool // 如果 dirty 与 read 中的不一致则为 true}
entry
type entry struct {p unsafe.Pointer // map 的 value 为指针类型}
load
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {// 从 read 中读取只读 mapread, _ := m.read.Load().(readOnly)e, ok := read.m[key]// 如果 read 中没有,且 read 和 dirty 不一致if !ok && read.amended {m.mu.Lock()// 双检查,避免获取锁的时候 dirty 被其他线程升级为 readread, _ = m.read.Load().(readOnly)e, ok = read.m[key]if !ok && read.amended {e, ok = m.dirty[key]m.missLocked() // miss++,到达限额 dirty 提升为 read}m.mu.Unlock()}if !ok {return nil, false}return e.load()}
dirty 升级为 read 过程
func (m *Map) missLocked() {m.misses++if m.misses < len(m.dirty) {return}// misses 长度增长到等于 dirty 时,将 read 中的内容原子更新为 dirtym.read.Store(readOnly{m: m.dirty})m.dirty = nilm.misses = 0}
