先说问题
Golang的原生Map是不支持并发写的,但是可以并发读,是一种非线程安全的结构。以下程序直接报错: fatal error: concurrent map read and map write,即便访问的是不同的key。
解决方案
Golang 1.9之后的版本加入了sync.Map结构,一种并发安全的结构,源码中这样介绍

翻译过来就是(1)写一次但是读取多次,只会增加缓存的长度而已(2)多线程读写不同的键能够明显减少锁的争用。
-源码解析
1、基础结构
sync.Map结构利用了两个原生map结构读写,read主要负责读,而dirty主要负责写,只需要在合适的时机进行同步即可。此外,read和dirty的map的值存的都是指针,指向都是同一份数据,所以不会有太大的内存消耗,而且修改read[key]那么dirty[key]的值也被修改了。
2、常规方法:增改、删、查
增改:store函数。重点关注对象dirty成员;只要dirty不为nil,store函数执行完必须保证dirty不能落后于read【坑位3:为什么】。扩容只会发生在dirty上,读read不影响。
删:delete函数。
查:load函数。可以看出读是不会立刻加锁的
注意的一些点可能会帮助理解:
1、结论1:value是expunged那么dirty一定不为nil。value被标记为expunged的唯一方式就是在store函数dirtyLocked()-> tryExpungeLocked(),同时会将amended设置为true,这两个操作是绑定的。所以只要有value是expunged,那么amended一定为true,也就表示dirty不落后于read的,而且可以看出expunged一定是只会出现在read里面的key对应的value上,dirty是一定不会有的,毕竟这是唯一设置expunged的位置。
2、结论2:dirty只要不为nil,那么dirty就一定不会落后于read。再查看一下dirty被新增或者删除的代码位置【置为空,修改值不算,因为都不会影响dirty是否不落后于read】。(1)store函数中在read中找到了,但是value被标记为expunged,那么在dirty中新增该key,如第一点所说expunged存在就表示dirty不落后于read。(2)store函数中read中没找到且dirty中也没有,那么就会在dirty中新增,这种方式更不会导致dirty落后于read。所以只要dirty不为nil就一定会一直不落后于read的。
3、结论3:value是nil,那么dirty是nil要么非nil(废话),说白了nil就是一个寻常的值而以,就像你可以往map存放(key,nil),在这里作为了中间态(当然,中间态设置为1,2,3...都可以,只要是个对你没有意义的变量就行)。value被标记为nil有两种方式,一种是在store函数中unexpungeLocked()中,而触发这个语句的条件是value为expunged,同时如第一点所说,dirty是不落后于read的;另一种是在delete函数中,在read中找到了该key,那么如果该key对应value非nil且非expunged,那么就会更新为nil,而此时dirty可能为nil或者不为nil,但是不为nil的时候dirty也一定会有该key的,毕竟read都是从dirty中更新过去的,所以对于value正常的read有dirty一定有。
【填坑2:延迟删除】综上可以看出,在missLocked()中直接用dirty覆盖掉read的时候就会剔除掉了read中的expunged标识的(key,value),毕竟expunged标志只在read中的value才有。
【填坑3:为何不落后】因为涉及到用dirty更新read的操作,那么只要dirty不为nil就一定要不落后于read才行啊。