1. 程式人生 > >Golang併發安全字典sync.Map擴充套件

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裡面包含的鍵值對是實時的,並且不包含已經被邏輯刪除的鍵值對