欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-04 18:52:52
...

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

相关标签: Golang