Golang併發安全字典sync.Map擴充套件
阿新 • • 發佈:2018-11-03
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裡面包含的鍵值對是實時的,並且不包含已經被邏輯刪除的鍵值對