golang中map当前版本默认直接并发写会报concurrent map writes 错误
在golang中要实现并发读写的话有三种目前通用的方式:
1. 使用读写锁sync.RWMutex,在读的时候使用读锁,使用的时候如下代码,效率较低:
var counter = struct{
sync.RWMutex //读写锁
m map[string]int
}{m: make(map[string]int)}
counter.RLock() // 读锁定
n := counter.m["some_key"]
counter.RUnlock()
unter.Lock()// 写锁定
counter.m["some_key"]++
counter.Unlock()
2. 使用golang官方包的syncmap,效率比第一种方式要高。源码在原生包sync.Map,或者github:https://github.com/golang/sync/tree/master/syncmap
3. 使用其他开源包的concurrentmap,或者自己实现,可以参考java的concurrentmap,使用shard将数据分块,每个锁只针对一个块。
该文章主要讲syncmap的流程,并在源码中翻译原来注释加入一些中文注释帮助理解,配合源码食用效果更佳。
总述:
syncmap是由两个map构成,一个只读readmap,一个写dirtymap。两个map的存储的value都是entry一个指向具体值的指针,如果一个key在read和dirty中同时存在那么修改的时候通过read来原子修改即可,如果value为nil或者expunged代表被删除。
在代码里面通过原子操作+循环对比来实现lockfree。在操作read的时候使用的是lockfree的方式,操作dirty的时候需要加锁。
在每次lockfree进入lock之后,由于之前判断情况时候并没有上锁,所以是可能已经改变的,需要再取值判断一次情况。这种情况在整个源码从lockfree到lock时候大量出现。
读取
读的时候优先往read中lockfree读,read中读不到的话再往dirytymap 中lock查找,记录未命中次数,当达到一定次数之后将dirtymap中的数据全部复制到read中。
读取情况如下如下:
- 如果read中存在,返回结果
- read中没有,dirty没有生成,返回nil,记一次未命中数
- read中没有,返回dirty的结果,记一次为命中数
- 未命中数==len(dirty)的话,把read=dirty,dirty=nil
read中没有的意思是指没有对应key,entry指向nil,entry指向expunged
存储
在存储数据的时候只往dirtymap中写
存储情况如下:
- 当read中含有key,并且不为expunged时候,直接更新数据。为expunged说明已经存在dirty,并且该key没有进入dirty,需要走下面的情况更新dirty
- 当read中含有key,值为expunged时候,先插入entry到dirty中,然后更新值
- read中不含有,d