Golang并发安全字典sync.Map扩展
Go中自带的map不是并发安全的,sync.Map带来并发安全字典且提供增删改查常量级算法复杂度
sync.Map接收interface{}的键和值,作为约束sync.Map的键任然有限制:
限制依据:键类型必须是可判等的,具体如下
不能为map类型
不能为func类型
不能为slice类型
违背上述规则,引用sync.Map将引发panic
sync.Map虽然提供并发安全保证,但是并不提供类型安全保证(只会在内部检测类型不通过时抛出异常)
解决sync.Map类型安全方案有两个:
- 只让sync.Map存储某个特定类型的键,比如键只能是string类型,值只能是string类型
type StrToStrMap struct {
m sync.Map
}
func (imap *StrToStrMap) Delete(key string) {
imap.m.Delete(key)
}
func (imap *StrToStrMap) Load(key string) (value string, ok bool) {
v, ok := imap.m.Load(key)
if v != nil {
value = v.(string)
}
return
}
func (imap *StrToStrMap) LoadOrStore(key string, value string) (actual string, loaded bool) {
a, loaded := imap.m.LoadOrStore(key, value)
actual = a.(string)
return
}
func (imap *StrToStrMap) Range(f func(key string, value string) bool) {
fx := func(key , value interface{}) bool {
return f(key.(string), value.(string))
}
imap.m.Range(fx)
}
func (imap *StrToStrMap) Store(key string, value string) {
imap.m.Store(key, value)
}
这种方案的优点是:类型检查快速,可以借助编译特性过滤运行时检查,不会带来运行时性能损失
这种方案的缺点是:无法灵活改变Map键和值得类型,无法应对需求多样性的场景,否则需要再定义需要的映射类型会带来代码量的负担
2. 借助反射实现较为宽泛的键值约束,通过初始化类型时指定需要的键值对类型,然后在运行时动态检查类型是否符合预定义
type ConcurrentMap struct {
keyType reflect.Type
valueType reflect.Type
m sync.Map
}
//也可以在初始化时设定动态检测类型不通过时是否抛出panic异常
func NewConcurrentMap(keyType, valueType reflect.Type) (*ConcurrentMap, error) {
if keyType == nil {
return nil, errors.New("keyType is null")
}
if !keyType.Comparable() {
return nil, errors.New("keyType is not Comparable")
}
if valueType == nil {
return nil, errors.New("valueType is null")
}
imap := &ConcurrentMap{
keyType:keyType,
valueType:valueType,
}
return imap, nil
}
func (imap *ConcurrentMap) Delete(key reflect.Type) {
if reflect.TypeOf(key) != imap.keyType {
return
}
imap.m.Delete(key)
}
//isPanic 根据使用场景使用,当动态检测类型不通过时是否抛出运行时异常,发送异常时返回一个非nil的错误值
func (imap *ConcurrentMap) LoadOrStore(key, value reflect.Type, isPanic bool) (actual interface{}, loaded bool, errMsg error) {
if reflect.TypeOf(key) != imap.keyType {
if isPanic {
panic(fmt.Errorf("wrong key type: %v", reflect.TypeOf(key)))
} else {
return nil, false, fmt.Errorf("wrong key type: %v", reflect.TypeOf(key))
}
}
if reflect.TypeOf(value) != imap.valueType {
if isPanic {
panic(fmt.Errorf("wrong value type: %v", reflect.TypeOf(value)))
} else {
return nil, false, fmt.Errorf("wrong value type: %v", reflect.TypeOf(key))
}
}
actual, loaded = imap.m.LoadOrStore(key, value)
return
}
func (imap *ConcurrentMap) Range(f func(key, value interface{}) bool) {
imap.m.Range(f)
}
//isPanic 根据使用场景使用,当动态检测类型不通过时是否抛出运行时异常,发送异常时返回一个非nil的错误值
func (imap *ConcurrentMap) Store(key, value interface{}, isPanic bool) error {
if reflect.TypeOf(key) != imap.keyType {
if isPanic {
panic(fmt.Errorf("wrong key type: %v", reflect.TypeOf(key)))
} else {
return fmt.Errorf("wrong key type: %v", reflect.TypeOf(key))
}
}
if reflect.TypeOf(value) != imap.valueType {
if isPanic {
panic(fmt.Errorf("wrong value type: %v", reflect.TypeOf(value)))
} else {
return fmt.Errorf("wrong value type: %v", reflect.TypeOf(key))
}
}
imap.m.Store(key, value)
return nil
}
这种方案的优点是:可以灵活定制Map的键类型和值类型,应用场景广,可以配置动态检测类型不通过时是否panic
这种方案的缺点是:因为运用了reflect发射库来动态检测类型会带来性能上的损耗
总结:
sync.Map提供在保证类型安全的前提下可以并发存储,其内部使用原子值Value,pointer原子操作,以及少量的互斥锁来实现,简单来说:内部有两个使用原生map创建的只读map和脏map,这两个map在需要时可以交换。
只读map里面包含的键值对是不全的,因为只读map里面的键值对不能改变
脏map里面包含的键值对是实时的,并且不包含已经被逻辑删除的键值对
上一篇: 给VIM换个配色——安装主题
下一篇: 让Vim更好的工作——VIM映射