Go36-34,35-并發(fā)安全字典(sync.Map)-創(chuàng)新互聯(lián)

并發(fā)安全字典(sync.Map)

之前的幾篇,幾乎已經(jīng)把Go語(yǔ)言自帶的同步工具都講過(guò)了。這篇要講的是一個(gè)并發(fā)安全的高級(jí)數(shù)據(jù)結(jié)構(gòu):sync.Map。

我們提供的服務(wù)有:成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、滑縣ssl等。為上千企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢(xún)和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的滑縣網(wǎng)站制作公司

原生字典

Go語(yǔ)言自帶的字典類(lèi)型map,就是原生字典,并不是并發(fā)安全的。
在使用原生字典的時(shí)候,應(yīng)該在啟動(dòng)goroutine之前就完成字典的初始化和賦值?;蛘吒呒?jí)的做法是,可以在goroutine中,在首次使用的時(shí)候通過(guò)sync.Once來(lái)并發(fā)安全的完成初始化和賦值的操作,達(dá)到一個(gè)延遲初始化的優(yōu)化效果。之后在使用字典的時(shí)候,就只能獲取其中的內(nèi)容,不能再對(duì)其進(jìn)行修改了。這個(gè)在講sync.Once時(shí),在最后有示例。
如果是需要一個(gè)并發(fā)安全的,可以修改內(nèi)容的原生字典,也不是太麻煩。加上互斥鎖或讀寫(xiě)鎖就可以輕松實(shí)現(xiàn)了。下面就是一個(gè)自制的簡(jiǎn)易并發(fā)安全字典:

package main

import (
    "fmt"
    "sync"
)

// 一個(gè)自制的簡(jiǎn)易并發(fā)安全字典
type ConcurrentMap struct {
    m map[interface{}]interface{}
    mu sync.RWMutex
}

func NewConcurrentMap() *ConcurrentMap {
    return &ConcurrentMap{
        m: make(map[interface{}]interface{}),
    }
}

func (cm *ConcurrentMap) Delete(key interface{}) {
    cm.mu.Lock()
    defer cm.mu.Unlock()
    delete(cm.m, key)
}

func (cm *ConcurrentMap) Load(key interface{}) (value interface{}, ok bool) {
    cm.mu.RLock()
    defer cm.mu.RUnlock()
    value, ok = cm.m[key]
    return
}

func (cm *ConcurrentMap) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
    cm.mu.Lock()
    defer cm.mu.Unlock()
    actual, loaded = cm.m[key]
    if loaded {
        return
    }
    cm.m[key] = value
    actual = value
    return
}

func (cm *ConcurrentMap) Store(key, value interface{}) {
    cm.mu.Lock()
    defer cm.mu.Unlock()
    cm.m[key] = value
}

func (cm *ConcurrentMap) Range(f func(key, value interface{}) bool) {
    cm.mu.RLock()
    defer cm.mu.RUnlock()
    for k, v := range cm.m {
        if !f(k, v) {
            break
        }
    }
}

func main() {
    pairs := []struct{
        k string
        v int
    }{
        {"k1", 1},
        {"k2", 2},
        {"k3", 3},
        {"k4", 4},
    }

    {
        fmt.Println("創(chuàng)建map")
        cm := NewConcurrentMap()
        for i := range pairs {
            cm.Store(pairs[i].k, pairs[i].v)
        }
        fmt.Println(cm.m)

        fmt.Println("遍歷輸出map")
        cm.Range (func(k, v interface{}) bool {
            fmt.Printf("%v: %v\n", k, v)
            return true
        })

        fmt.Println("Load 和 LoadOrStore 方法")
        key := "k3"
        value, ok := cm.Load(key)
        fmt.Printf("Load %v: %v: %v\n", ok, key, value)
        value, ok = cm.LoadOrStore(key, 5)
        fmt.Printf("LoadOrStore %v: %v: %v\n", ok, key, value)
        key = "k5"
        value, ok = cm.Load(key)
        fmt.Printf("Load %v: %v: %v\n", ok, key, value)
        value, ok = cm.LoadOrStore(key, 5)
        fmt.Printf("LoadOrStore %v: %v: %v\n", ok, key, value)
        fmt.Println(cm.m)

        fmt.Println("Delete 方法")
        key = "k2"
        cm.Delete(key)
        fmt.Println(cm.m)
        key = "k21"
        cm.Delete(key)
        fmt.Println(cm.m)
    }
    fmt.Println("Over")
}

并發(fā)安全字典

Go言語(yǔ)官方是在Go 1.9中才正式加入了并發(fā)安全的字典類(lèi)型sync.Map。所以在這之前,已經(jīng)有很多庫(kù)提供了類(lèi)似的數(shù)據(jù)結(jié)構(gòu),其中有一些的性能在很大程度上有效的避免了的鎖的依賴(lài),性能相對(duì)會(huì)好一些。不過(guò)這些都是過(guò)去了,現(xiàn)在有了官方的并發(fā)安全的字典類(lèi)型sync.Map可以使用。
下面就是使用并發(fā)安全字典的示例:

package main

import (
    "fmt"
    "sync"
)

func main() {
    pairs := []struct{
        k string
        v int
    }{
        {"k1", 1},
        {"k2", 2},
        {"k3", 3},
        {"k4", 4},
    }

    {
        fmt.Println("創(chuàng)建map")
        cm := sync.Map{}
        for i := range pairs {
            cm.Store(pairs[i].k, pairs[i].v)
        }

        fmt.Println("遍歷輸出map")
        cm.Range (func(k, v interface{}) bool {
            fmt.Printf("%v: %v\n", k, v)
            return true
        })

        fmt.Println("Load 和 LoadOrStore 方法")
        key := "k3"
        value, ok := cm.Load(key)
        fmt.Printf("Load %v: %v: %v\n", ok, key, value)
        value, ok = cm.LoadOrStore(key, 5)
        fmt.Printf("LoadOrStore %v: %v: %v\n", ok, key, value)
        key = "k5"
        value, ok = cm.Load(key)
        fmt.Printf("Load %v: %v: %v\n", ok, key, value)
        value, ok = cm.LoadOrStore(key, 5)
        fmt.Printf("LoadOrStore %v: %v: %v\n", ok, key, value)

        fmt.Println("Delete 方法")
        key = "k2"
        cm.Delete(key)
        key = "k21"
        cm.Delete(key)

        fmt.Println("遍歷輸出map")
        cm.Range (func(k, v interface{}) bool {
            fmt.Printf("%v: %v\n", k, v)
            return true
        })
    }
    fmt.Println("Over")
}

這里用起來(lái)和上面原生加鎖的字典沒(méi)太大差別,其實(shí)上面的例子里的方法名稱(chēng)和功能應(yīng)該就是參照這里寫(xiě)的。

并發(fā)安全字典與原生字典的比較

這里從3個(gè)方面對(duì)并發(fā)安全字典和原生字典進(jìn)行了比較。
算法復(fù)雜度
這個(gè)字典類(lèi)型提供了一些常用的鍵值存取操作方法,并保證了這些操作的并發(fā)安全。同時(shí),它的存、取、刪除等操作都可以基本保證在常數(shù)時(shí)間內(nèi)執(zhí)行完畢。就是說(shuō),它的算法復(fù)雜度與原生字典一樣都是O(1)的。
效率和鎖
與單純使用原生字典和互斥鎖的用法相比,使用并發(fā)安全字典可以顯著減少鎖的爭(zhēng)奪。雖然并發(fā)安全字典本身也用到了鎖,但是它在盡可能的避免使用鎖。使用鎖,就以為著要把一些并發(fā)的操作強(qiáng)制串行化,這就會(huì)降低程序的性能。因此,在講原子操作的時(shí)候,就建議能用原子操作就不要用鎖。不過(guò)當(dāng)時(shí)也說(shuō)了,原子操作支持的數(shù)據(jù)類(lèi)型有局限性。
編譯器的支持
無(wú)論在何種場(chǎng)景下使用并發(fā)安全字典,都需要記得,它與原生字典是明顯不同的。并發(fā)安全字典只有Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的一員,而原生字典是語(yǔ)言層面的東西。正是因?yàn)檫@個(gè)原因,Go語(yǔ)言的編譯器不會(huì)對(duì)它的鍵和值進(jìn)行特殊的類(lèi)型檢查。它所有的方法涉及的鍵和值類(lèi)型都是空接口。所以,必須在程序中自行保證它的鍵類(lèi)型和值類(lèi)型的正確性。

并發(fā)安全字典key類(lèi)型的要求

原生字典的key不能是如下的類(lèi)型:

  • 不能是,函數(shù)類(lèi)型
  • 不能是,字典類(lèi)型
  • 不能是,切片類(lèi)型

并發(fā)安全字典的key的類(lèi)型也是一樣的限制。
在并發(fā)安全字典內(nèi)部,使用的存儲(chǔ)介質(zhì)仍然是原生字典,上面這些類(lèi)型都不能用。但是由于并發(fā)安全字典的key類(lèi)型的實(shí)際類(lèi)型是空接口,就是可以是任何類(lèi)型,所以編譯器是允許使用任何類(lèi)型的。如果使用了上述key不支持的類(lèi)型,編譯階段是發(fā)現(xiàn)不了的。只要在程序運(yùn)行期間才能確定鍵的實(shí)際類(lèi)型,此時(shí)如果是不正確的key值實(shí)際類(lèi)型肯定會(huì)引發(fā)panic。
因此,一定不要違反字典對(duì)key類(lèi)型的限制。應(yīng)該在每次操作并發(fā)安全字典的時(shí)候,都去顯式的檢查key值的實(shí)際類(lèi)型。無(wú)論是存、取、刪除都是如此。
更好的做法是,把針對(duì)同一個(gè)并發(fā)安全字典的幾種操作都集中起來(lái),然后統(tǒng)一的編寫(xiě)檢查代碼。如果是把并發(fā)安全字典封裝在一個(gè)結(jié)構(gòu)體里,是一個(gè)很好的選擇。
總之,必須保證key的類(lèi)型是可比較的(或者說(shuō)是可判等的)。如果實(shí)在確定不了,那么可以先通過(guò)調(diào)用reflect.TypeOf函數(shù)得到一個(gè)鍵對(duì)應(yīng)的反射類(lèi)型值(即:reflect.Type類(lèi)型),然后再調(diào)用這個(gè)值的Comparable方法,得到確切的判斷結(jié)果。Comparable方法返回一個(gè)布爾值,判斷類(lèi)型是否是可比較的,即:判斷是否可以作為key的類(lèi)型。

保證key和value的類(lèi)型正確

簡(jiǎn)單說(shuō),可以使用類(lèi)型斷言表達(dá)式或者反射操作來(lái)保證類(lèi)型正確。為了進(jìn)一步明確并發(fā)安全字典中key值的實(shí)際類(lèi)型,這里大致有兩個(gè)方案。

只能存儲(chǔ)特定的類(lèi)型

這個(gè)是方案一:讓并發(fā)安全字典只能存儲(chǔ)某個(gè)特定類(lèi)型的key。
指定key只能是某個(gè)類(lèi)型,一旦完全確定了key的類(lèi)型,就可以在存、取、刪除的時(shí)候,使用類(lèi)型斷言表達(dá)式去對(duì)key的類(lèi)型做檢查了。一般這種檢查并不繁瑣,而且要是把并發(fā)安全字典封裝在一個(gè)結(jié)構(gòu)體里,就更方便了。這時(shí)Go語(yǔ)言編譯器就可以幫助完成檢查的工作。就像下面這樣:

// 這是一個(gè)key為字符串類(lèi)型,value為數(shù)字的字典
type StrIntMap struct {
    m sync.Map
}

func (ms *StrIntMap) Delete(key int) {
    ms.m.Delete(key)
}

func (ms *StrIntMap) Load(key int) (value int, ok bool) {
    v, ok := ms.m.Load(key)
    if v != nil {
        value = v.(int)
    }
    return
}

func (ms *StrIntMap) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
    a, loaded := ms.m.LoadOrStore(key, value)
    actual = a.(int)
    return
}

func (ms *StrIntMap) Range(f func(key string, value int) bool) {
    f1 := func(key, value interface{}) bool {
        return f(key.(string), value.(int))
    }
    ms.m.Range(f1)
}

func (ms *StrIntMap) Store(key string, value int) {
    ms.m.Store(key, value)
}

把并發(fā)安全字典封裝到結(jié)構(gòu)體中之后,要把原本字典中公開(kāi)的方法在結(jié)構(gòu)體上再實(shí)現(xiàn)一遍。只要簡(jiǎn)單的調(diào)用并發(fā)安全字典里的方法就可以了。由于自己寫(xiě)的結(jié)構(gòu)體的方法的參數(shù)列表里已經(jīng)明確的定義了變量的類(lèi)型,所以就不用再做類(lèi)型檢查了,并且使用編譯器編寫(xiě)代碼的時(shí)候類(lèi)型錯(cuò)誤也會(huì)有提示?;谏厦娴那闆r,使用這些方法取出key和value的時(shí)候,完全不用擔(dān)心類(lèi)型不正確,因?yàn)檎_性在最初存入的時(shí)候就已經(jīng)保證了。這里在取出值的時(shí)候,由于sync.Map返回的是空接口,所以需要用類(lèi)型斷言做一下類(lèi)型轉(zhuǎn)換。這里用類(lèi)型斷言的時(shí)候只使用一個(gè)返回值,直接轉(zhuǎn),不用擔(dān)心會(huì)有錯(cuò)誤。

使用反射

上一個(gè)方案雖然很好,但是有一點(diǎn)不方便。就是封裝了一大堆內(nèi)容,實(shí)現(xiàn)了一個(gè)key-value的類(lèi)型。如果還是需要另外一個(gè)類(lèi)型的字典,還得再封裝同樣的一大堆的內(nèi)容。這樣的話,在需求多樣化之后,工作量反而更大,而且會(huì)有很多雷同的代碼。
這里需要一個(gè)這樣效果的方案:既要保持sync.Map類(lèi)型原有的靈活性,又可以約束key和value的類(lèi)型。
這個(gè)是方案二:就是通過(guò)反射來(lái)實(shí)現(xiàn)做類(lèi)型的判斷

結(jié)構(gòu)體的類(lèi)型

這次在設(shè)計(jì)結(jié)構(gòu)體類(lèi)型的時(shí)候,只包含sync.Map類(lèi)型的字段就不夠了,需要向下面這樣:

type ConcurrentMap struct {
    m         sync.Map
    keyType   reflect.Type
    valueType reflect.Type
}

這次多定義了2個(gè)字段keyType和valueType,分別用于保存key類(lèi)型和value類(lèi)型。類(lèi)型都是reflect.Type,就是反射類(lèi)型。這個(gè)類(lèi)型可以代表Go語(yǔ)言的任何數(shù)據(jù)類(lèi)型,并且類(lèi)型的值也非常容易獲得:就是通過(guò)reflect.TypeOf函數(shù)并把某個(gè)樣本值傳入即可,比如:reflect.TypeOf(int(3)),就是int類(lèi)型的反射類(lèi)型值。

構(gòu)造函數(shù)

首先提供一個(gè)方法,讓外部的代碼可以創(chuàng)建一個(gè)結(jié)構(gòu)體:

func NewConcurrentMap(keyType, valueType reflect.Type) (*ConcurrentMap, error) {
    if keyType == nil {
        return nil, fmt.Errorf("key 類(lèi)型為 nil")
    }
    if !keyType.Comparable() { // 判斷類(lèi)型是否可以比較,就是是否是內(nèi)做key的類(lèi)型
        return nil, fmt.Errorf("不可比較的類(lèi)型: %s", keyType)
    }
    if valueType == nil {
        return nil, fmt.Errorf("value 類(lèi)型為 nil")
    }
    cm := &ConcurrentMap{
        keyType:   keyType,
        valueType: valueType,
    }
    return cm, nil
}

創(chuàng)建成功,則返回結(jié)構(gòu)體的指針和nil的錯(cuò)誤類(lèi)型。創(chuàng)建失敗,則返回nil和具體的錯(cuò)誤類(lèi)型。
先判斷傳入的類(lèi)型是否為nil。另外對(duì)于key,基于對(duì)key類(lèi)型的限制,必須是可比較的,這里用reflect包里的Comparable方法就可以進(jìn)行判斷了。

Load方法

結(jié)構(gòu)體有了,接下來(lái)就實(shí)現(xiàn)所有的方法。首先是Load方法:

func (cm *ConcurrentMap) Load(key interface{}) (value interface{}, ok bool) {
    if reflect.TypeOf(key) != cm.keyType{
        return
    }
    return cm.m.Load(key)
}

這里的類(lèi)型檢查的代碼非常簡(jiǎn)單。如果參數(shù)key的反射類(lèi)型與keyTpye字段的反射類(lèi)型不相等,那么就直接返回空值。類(lèi)型都不對(duì),那么字典里是一定沒(méi)有這個(gè)key的,所以就直接返回查詢(xún)不到的結(jié)果。這時(shí),返回的是nil和false,完全符合Load方法原本的含義。

Store方法

Stroe方法接收2個(gè)空接口類(lèi)型,沒(méi)有返回值:

func (cm *ConcurrentMap) Store(key, value interface{}) {
    if reflect.TypeOf(key) != cm.keyType {
        panic(fmt.Errorf("key類(lèi)型不符合: 實(shí)際類(lèi)型: %v, 需要類(lèi)型: %v\n", reflect.TypeOf(key), cm.keyType))
    }
    if reflect.TypeOf(value) != cm.valueType {
        panic(fmt.Errorf("value類(lèi)型不符合: 實(shí)際類(lèi)型: %v, 需要類(lèi)型: %v\n", reflect.TypeOf(value), cm.valueType))
    }
}

這里的做法是一旦類(lèi)型檢查不符,就直接引發(fā)panic。這么做主要是由于Store方法沒(méi)有結(jié)果聲明,就是無(wú)返回值,所以在參數(shù)值有問(wèn)題的時(shí)候,無(wú)法通過(guò)比較平和的方式告知調(diào)用方。這么做也是符合Store方法的原本含義的。
也可以把結(jié)構(gòu)體的Store方法改的和sysc.Map里的Store方法稍微不一樣一點(diǎn),就是加上結(jié)果聲明,返回一個(gè)error類(lèi)型。這樣當(dāng)參數(shù)值類(lèi)型不正確的時(shí)候,就返回響應(yīng)的錯(cuò)誤,而不引發(fā)panic。作為示例,就先用panic的方式吧。在實(shí)際的應(yīng)用場(chǎng)景里可以再做優(yōu)化和改進(jìn)。

完整的定義

其他的方法就都差不多了,下面展示了所有定義的方法:

type ConcurrentMap struct {
    m         sync.Map
    keyType   reflect.Type
    valueType reflect.Type
}

func NewConcurrentMap(keyType, valueType reflect.Type) (*ConcurrentMap, error) {
    if keyType == nil {
        return nil, fmt.Errorf("key 類(lèi)型為 nil")
    }
    if !keyType.Comparable() { // 判斷類(lèi)型是否可以比較,就是是否是內(nèi)做key的類(lèi)型
        return nil, fmt.Errorf("不可比較的類(lèi)型: %s", keyType)
    }
    if valueType == nil {
        return nil, fmt.Errorf("value 類(lèi)型為 nil")
    }
    cm := &ConcurrentMap{
        keyType:   keyType,
        valueType: valueType,
    }
    return cm, nil
}

func (cm *ConcurrentMap) Load(key interface{}) (value interface{}, ok bool) {
    if reflect.TypeOf(key) != cm.keyType {
        return
    }
    return cm.m.Load(key)
}

func (cm *ConcurrentMap) Store(key, value interface{}) {
    if reflect.TypeOf(key) != cm.keyType {
        panic(fmt.Errorf("key類(lèi)型不符合: 實(shí)際類(lèi)型: %v, 需要類(lèi)型: %v\n", reflect.TypeOf(key), cm.keyType))
    }
    if reflect.TypeOf(value) != cm.valueType {
        panic(fmt.Errorf("value類(lèi)型不符合: 實(shí)際類(lèi)型: %v, 需要類(lèi)型: %v\n", reflect.TypeOf(value), cm.valueType))
    }
    cm.m.Store(key, value)
}

func (cm *ConcurrentMap) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
    if reflect.TypeOf(key) != cm.keyType {
        panic(fmt.Errorf("key類(lèi)型不符合: 實(shí)際類(lèi)型: %v, 需要類(lèi)型: %v\n", reflect.TypeOf(key), cm.keyType))
    }
    if reflect.TypeOf(value) != cm.valueType {
        panic(fmt.Errorf("value類(lèi)型不符合: 實(shí)際類(lèi)型: %v, 需要類(lèi)型: %v\n", reflect.TypeOf(value), cm.valueType))
    }
    actual, loaded = cm.m.LoadOrStore(key, value)
    return
}

func (cm *ConcurrentMap) Delete(key interface{}) {
    if reflect.TypeOf(key) != cm.keyType {
        return
    }
    cm.m.Delete(key)
}

func (cm *ConcurrentMap) Range(f func(key, value interface{}) bool) {
    cm.m.Range(f)
}

總結(jié)

第一種方案,適用于可以完全確定key和value的具體類(lèi)型的情況。這是,可以利用Go語(yǔ)言的編譯器去做類(lèi)型檢查,并用類(lèi)型斷言表達(dá)式作為輔助。缺陷是一次定義只能滿足一種字典類(lèi)型,一旦字典類(lèi)型多樣化,就需要編寫(xiě)大量的重復(fù)代碼。不過(guò)還有一個(gè)好處,就是在編譯階段就可以發(fā)現(xiàn)類(lèi)型不正確的問(wèn)題。
第二種方案,在程序運(yùn)行之前不用明確key和value的類(lèi)型,只要在初始化并發(fā)安全字典的時(shí)候,動(dòng)態(tài)的給定key和value的類(lèi)型即可。主要是用到了reflect包中的方法和數(shù)據(jù)類(lèi)型。靈活性高了,一次就能滿足所有的字典類(lèi)型。但是那些反射操作或多或少都會(huì)降低程序的性能。而且如果有類(lèi)型不符合的情況,無(wú)法在編譯階段發(fā)現(xiàn)。

并發(fā)安全字典內(nèi)部的實(shí)現(xiàn)

保證并發(fā)安全,還要保證效率,主要的問(wèn)題就是要盡量避免使用鎖。
sync.Map類(lèi)型在內(nèi)部使用了大量的原子操作來(lái)存取key和value,并使用了兩個(gè)原生的字典作為存儲(chǔ)介質(zhì)。

只讀字典

其中一個(gè)原生的字典是sync.Map的read字段,類(lèi)型是sync/atomic.Value類(lèi)型。這個(gè)原生字典可以被看作一個(gè)快照,總會(huì)在條件滿足時(shí),去重新保存所屬的sync.Map值中包含的所有key-value對(duì)。這里叫它只讀字典,它雖然不會(huì)增減其中的key,但是允許改變其中的key所對(duì)應(yīng)的value。所以這個(gè)只讀特性只是對(duì)于所有的key的。
因?yàn)槭莝ync/atomic.Value類(lèi)型,所以都是原子操作,不用鎖。另外,這個(gè)只讀字典在存儲(chǔ)key-value對(duì)的時(shí)候,對(duì)value還做了一層封裝。先把值轉(zhuǎn)換為了unsafe.Pointer類(lèi)型的值,然后再把unsafe.Pointer也封裝,并存儲(chǔ)在其中的原生字典中。如此一來(lái),在變更某個(gè)key所對(duì)應(yīng)的value的時(shí)候,就可以使用原子操作了。

臟字典

另一個(gè)原生字典是sync.Map的dirty字段,它的key類(lèi)型是空接口。它存儲(chǔ)key-value對(duì)的方式與read字段中的原生字典一致,并且也是把value的值先做轉(zhuǎn)換和封裝后再進(jìn)行存儲(chǔ)的。這里叫它臟字典。
有點(diǎn)不太好理解,看下源碼里的類(lèi)型定義幫助理解只讀字典和臟字典的類(lèi)型:

type Map struct {
    mu Mutex
    read atomic.Value // readOnly
    dirty map[interface{}]*entry
    misses int
}

// readOnly is an immutable struct stored atomically in the Map.read field.
type readOnly struct {
    m       map[interface{}]*entry
    amended bool // true if the dirty map contains some key not in m.
}

type entry struct {
    p unsafe.Pointer // *interface{}
}

存取值的過(guò)程

了解下sync.Map是如何巧妙的使用它的2個(gè)原生字典的。分析各個(gè)方法在執(zhí)行時(shí)具體進(jìn)行的操作,再判斷這些操作對(duì)性能的影響。

獲取值的過(guò)程
sync.Map在查找指定的key所對(duì)應(yīng)的value的時(shí)候,會(huì)先去只讀字典中查找,只讀字典是原子操作,不要要鎖。只有當(dāng)只讀字典里沒(méi)有,再去臟字典里查找,訪問(wèn)臟字典需要加鎖。

存儲(chǔ)值的過(guò)程
在存key-value的時(shí)候,只要只讀字典中已經(jīng)存在這個(gè)key了,并且未被標(biāo)記為刪除,就會(huì)把新值直接存到value里然后直接返回,還是不需要加鎖。否則,才會(huì)加鎖后,把新的key-value加到臟字典里。這個(gè)時(shí)候還要抹去只讀字典里的刪除標(biāo)記。

刪除標(biāo)記
當(dāng)一個(gè)kye-value對(duì)應(yīng)該被刪除,但是仍然存在于只讀字典中的時(shí)候,會(huì)用標(biāo)記來(lái)標(biāo)記刪除,但是還不會(huì)直接武林刪除。
這種情況會(huì)在重建臟字典以后的一段時(shí)間內(nèi)出現(xiàn)。不過(guò),過(guò)不了多久,還是會(huì)被真正的刪除掉的。在查找和遍歷的時(shí)候,有刪除標(biāo)記的key-value對(duì)會(huì)忽略的。

刪除的過(guò)程
刪除key-value對(duì),sync.Map會(huì)先檢查只讀字典中是否有對(duì)應(yīng)的key。如果沒(méi)有,可能會(huì)在臟字典中,那就加鎖然后試圖從臟字典里刪掉該key-value對(duì)。最后,sync.Map會(huì)把key-value對(duì)中指向value的那個(gè)指針置為nil,這是另一種邏輯刪除的方式,臟字點(diǎn)重建之后自然就真正刪除了。

重建臟字典
只讀字典和臟字典之間是會(huì)互相轉(zhuǎn)換的。在臟字典中查找key-value對(duì)足夠次數(shù)時(shí),sync.Map會(huì)把臟字典直接作為只讀字典,保存到read字段中。然后把dirty字段置為nil。之后一旦再有新的key-value存入,就會(huì)依據(jù)只讀字典去重建臟字典,會(huì)把只讀字典標(biāo)記為刪除的key-value對(duì)過(guò)濾掉。這些轉(zhuǎn)換操作都有臟字典,所以都需要加鎖。

小結(jié)
sync.Map的只讀字典和臟字典中的key-value對(duì)并不是實(shí)時(shí)同步的。由于只讀字典的key不能改變,所以其中的key-value對(duì)可能是不全的。而臟字典中的key-value對(duì)總是全的,不多也不少。新的key-value對(duì)只讀字典里沒(méi)有;已經(jīng)標(biāo)記刪除的key-value對(duì)物理上還在只讀字典里,只是加了標(biāo)記。
在讀操作為主,而寫(xiě)操作很少的情況下,并發(fā)安全字典的性能會(huì)更好。在幾個(gè)寫(xiě)操作中,新增key-value對(duì)的操作對(duì)并發(fā)安全字典的性能影響大,其次是刪除操作。修改操作影響略小,只讀字典里如果有這個(gè)key的話,并且沒(méi)有標(biāo)記刪除,直接在只讀字典里把value改掉,不用加鎖,對(duì)性能影響就會(huì)很小。

創(chuàng)新互聯(lián)www.cdcxhl.cn,專(zhuān)業(yè)提供香港、美國(guó)云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開(kāi)啟,新人活動(dòng)云服務(wù)器買(mǎi)多久送多久。

新聞標(biāo)題:Go36-34,35-并發(fā)安全字典(sync.Map)-創(chuàng)新互聯(lián)
本文鏈接:http://bm7419.com/article28/dicijp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、軟件開(kāi)發(fā)App開(kāi)發(fā)、網(wǎng)站建設(shè)網(wǎng)站維護(hù)、Google

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)