sync.Map简述
简单来说,sync.Map是并发版本的map,golang自带的map在并发操作下会触发panic。sync.Map支持Load
,Store
, LoadOrStore
,Range
,Delete
操作。其中Range支持在多goroutine下运作,能确保每个key最多被处理一次,但是无法保证遍历过程中实时同步其他goroutine的增删操作。
使用sync.Map
你真的需要sync.Map吗?
比起sync.Map如何使用,一个更重要的问题在于我们是否真的需要sync.Map。的确如我们之前所说,sync.Map支持并发而map不支持,但是我们可以很轻松的用RWMutex
或者Mutex
结合map来支持并发,而且比起sync.Map,这两种方案更好的支持类型安全,而且在绝大多数时候性能更优。一个简单的答案是,如果你不知道为什么需要sync.Map,那么很可能你不需要sync.Map。
那么什么时候我们需要sync.Map呢?要解答这个问题,我们不妨回顾下sync.Map被提出的原因。简单来说,sync.Map被提出是因为google发现,RWLock配合map方案在高读取+多核cpu上表现不佳(具体可以翻看overview of sync map)。因此,snyc.Map就是为了改善多核高读取低写入时候的性能而引入。
具体来说,如果你的应用有以下的属性,那么可以考虑使用,否则更加建议使用RWMutex
或者Mutex
结合map的方案
- 如果写入的key是稳定的(极少)
- 如果不同goroutine对key的访问是不同的
如何更好地使用sync.Map
sync.Map有着非常简单的API,如果你需要存储新的键值对,你可以使用Store
,如果你需要读取键值对,你可以使用Load
;如果需要删除某个键值对,那么使用Delete
;如果你需要遍历整个map,Range
在那里等你。
但是我们如果观察sync.Map的API,我们会发现,为了考虑通用性,所有的key和value都是interface{}
,换言之,我们失去了类型检查提供的安全性而且被迫更多的使用类型断言。于是你面临着两种选择:在每次调用API后都小心翼翼地使用类型断言,你的代码里面出现无数的if v,ok=value.(xxType);!ok{}
;亦或者每一次都直接使用v.(xxType)
直到你的进程在某些关键时刻宕机让你抓耳挠腮为止。那么我们还有第三种方案吗?庆幸的是,答案是有的,不过需要我们做出一些小小的努力。
如果你的map在使用的时候有明确类型,一个简单的思路是封装snyc.Map并且对外提供指定类型的Load
,Delete
、Store
等等
type StringMap struct{
m sync.Map
}
func (s *StringMap) Store(key,value string){
s.m