Golang并发安全字典sync.Map扩展

Go中自带的map不是并发安全的,sync.Map带来并发安全字典且提供增删改查常量级算法复杂度
sync.Map接收interface{}的键和值,作为约束sync.Map的键任然有限制:
限制依据:键类型必须是可判等的,具体如下
不能为map类型
不能为func类型
不能为slice类型
违背上述规则,引用sync.Map将引发panic
sync.Map虽然提供并发安全保证,但是并不提供类型安全保证(只会在内部检测类型不通过时抛出异常)
解决sync.Map类型安全方案有两个:

  1. 只让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里面包含的键值对是实时的,并且不包含已经被逻辑删除的键值对

猜你喜欢

转载自blog.csdn.net/u010129985/article/details/83586318