GO并发安全字典sync.map(1)
sync.Map
一个并发安全的高级数据结构。这个字典类型提供了一些常用的键值存取操作方法,并保证了这些操作的并发安全。同时,它的存、取、删等操作都可以基本保证在常数时间内执行完毕。换句话说,它们的算法复杂度与map类型一样都是O(1)。于在2017年发布的Go 1.9中正式加入了并发安全的字典类型sync.Map
思考
设想,如果让你自己实现一个sync map,你回怎么做?
优化
与单纯使用原生map和互斥锁的方案相比,使用sync.Map可以显著地减少锁的争用。sync.Map本身虽然也用到了锁,但是,它其实在尽可能地避免使用锁。使用锁就意味着要把一些并发的操作强制串行化。这往往会降低程序的性能,尤其是在计算机拥有多个CPU核心的情况下。因此,我们常说,能用原子操作就不要用锁。
限制与要求
由于并发安全字典内部使用的存储介质正是原生字典,所以对原生字典的限制要求同样适用于sync.map.
键的实际类型不能是函数类型、字典类型和切片类型
使用建议
我们应该在每次操作并发安全字典的时候,都去显式地检查键值的实际类型。无论是存、取还是删,都应该如此。
更好的做法是,把针对同一个并发安全字典的这几种操作都集中起来,然后统一地编写检查代码。把并发安全字典封装在一个结构体类型中。
我们必须保证键的类型是可比较的(或者说可判等的)。如果你实在拿不准,那么可以先通过调用reflect.TypeOf函数得到一个键值对应的反射类型值(即:reflect.Type类型的值),然后再调用这个值的Comparable方法,得到确切的判断结果.
保证并发安全字典中的键和值的类型正确性方案
方案一:
type IntStrMap struct {
m sync.Map
}
func (iMap *IntStrMap) Delete(key int) {
iMap.m.Delete(key)
}
func (iMap *IntStrMap) Load(key int) (value string, ok bool) {
v, ok := iMap.m.Load(key)
if v != nil {
value = v.(string)
}
return
}
func (iMap *IntStrMap) LoadOrStore(key int, value string) (actual string, loaded bool) {
a, loaded := iMap.m.LoadOrStore(key, value)
actual = a.(string)
return
}
func (iMap *IntStrMap) Range(f func(key int, value string) bool) {
f1 := func(key, value interface{}) bool {
return f(key.(int), value.(string))
}
iMap.m.Range(f1)
}
func (iMap *IntStrMap) Store(key int, value string) {
iMap.m.Store(key, value)
}
指定key的类型和value 值得类型,由编译器帮我们校验。
优点:是简单方便高效
缺点:灵活性差,如果你有多种key,value 的组合就得写很多雷同的代码。
方案二:
封装的结构体类型的所有方法,都可以与sync.Map类型的方法完全一致(包括方法名称和方法签名)。
在这些方法中,我们就需要添加一些做类型检查的代码了。另外,这样并发安全字典的键类型和值类型,必须在初始化的时候就完全确定。
type ConcurrentMap struct {
m sync.Map
keyType reflect.Type
valueType reflect.Type
}
func (cMap *ConcurrentMap) Load(key interface{}) (value interface{}, ok bool) {
if reflect.TypeOf(key) != cMap.keyType {
return
}
return cMap.m.Load(key)
}
func (cMap *ConcurrentMap) Store(key, value interface{}) {
if reflect.TypeOf(key) != cMap.keyType {
panic(fmt.Errorf("wrong key type: %v", reflect.TypeOf(key)))
}
if reflect.TypeOf(value) != cMap.valueType {
panic(fmt.Errorf("wrong value type: %v", reflect.TypeOf(value)))
}
cMap.m.Store(key, value)
}
存的时候通过反射取到key和value类型,与指定类型不符直接panic。也可以返回个 ok bool 。如果你不想太粗暴。
取的时候通过反射取到key类型,与指定类型不符直接返回。
这种情况下必须先要保证键和值的类型是可比较的。
方案总结
第一种方案存在一个很明显的缺陷,那就是无法灵活地改变字典的键和值的类型。一旦需求出现多样化,编码的工作量就会随之而来。
第二种方案很好地弥补了这一缺陷,但是,那些反射操作或多或少都会降低程序的性能。我们往往需要根据实际的应用场景,通过严谨且一致的测试,来获得和比较程序的各项指标,并以此作为方案选择的重要依据之一。