golang-map是線程安全的嗎?

map

字典(map)它能存儲(chǔ)的不是單一值的集合,而是鍵值對(duì)的集合。

什么是鍵值對(duì)?它是從英文 key-value pair 直譯過(guò)來(lái)的一個(gè)詞。顧名思義,一個(gè)鍵值對(duì)就代表
了一對(duì)鍵和值。一個(gè)“鍵”和一個(gè)“值”分別代表了一個(gè)從屬于某一類(lèi)型的獨(dú)立值,把它們兩個(gè)捆綁在一
起就是一個(gè)鍵值對(duì)了。在 Go 語(yǔ)言規(guī)范中,應(yīng)該是為了避免歧義,他們將鍵值對(duì)換了一種稱(chēng)呼,
叫做:“鍵 - 元素對(duì)”。
Go 語(yǔ)言的字典類(lèi)型其實(shí)是一個(gè)哈希表(hash table)的特定實(shí)現(xiàn)。在這個(gè)實(shí)現(xiàn)中,鍵和元素的
最大不同在于, 的類(lèi)型是受限的,而元素對(duì)卻可以是任意類(lèi)型的。

代碼:

    ma := make(map[string]string)

    ma["key"] = "value"http:// 存儲(chǔ)

    value, ok := ma["key"]//獲取值。ok:獲取狀態(tài),value:ok=true說(shuō)明有值,否則反之
    fmt.Println(value, ok)
    
    //遍歷
    for key, value := range ma {
        fmt.Printf("key : %s, value:%s \n", key, value)
    }
    //刪除
    delete(ma, "key")
    

map:不是線程安全的。在同一時(shí)間段內(nèi),讓不同 goroutine 中的代碼,對(duì)同一個(gè)字典進(jìn)行讀寫(xiě)操作是不安全
的。字典值本身可能會(huì)因這些操作而產(chǎn)生混亂,相關(guān)的程序也可能會(huì)因此發(fā)生不可預(yù)知的問(wèn)題。

sync.Map

在 2017 年發(fā)布的 Go 1.9 中正式加入了并發(fā)安全的字典類(lèi)型sync.Map。這個(gè)字典類(lèi)型提供了一些常用的鍵值存取操作方法,并保證了這些操作的并發(fā)安全。同時(shí),它的存、取、刪等操作都可以基本保證在常數(shù)時(shí)間內(nèi)執(zhí)行完畢。換句話說(shuō),它們的算法復(fù)雜度與map類(lèi)型一樣都是O(1)的。在有些時(shí)候,與單純使用原生map和互斥鎖的方案相比,使用sync.Map可以顯著地減少鎖的爭(zhēng)用。sync.Map本身雖然也用到了鎖,但是,它其實(shí)在盡可能地避免使用鎖。

代碼:

    var ma sync.Map// 該類(lèi)型是開(kāi)箱即用,只需要聲明既可
    ma.Store("key", "value") // 存儲(chǔ)值
    ma.Delete("key") //刪除值
    ma.LoadOrStore("key", "value")// 獲取值,如果沒(méi)有則存儲(chǔ)
    fmt.Println(ma.Load("key"))//獲取值
    
    //遍歷
    ma.Range(func(key, value interface{}) bool {
        fmt.Printf("key:%s ,value:%s \n", key, value)
        //如果返回:false,則退出循環(huán),
        return true
    })

擴(kuò)展:

為什么map的鍵會(huì)有限制?

Go 語(yǔ)言規(guī)范規(guī)定,在鍵類(lèi)型的值之間必須可以施加操作符==和!=。換句話說(shuō),鍵類(lèi)型的值必須要支持判等操作。由于函數(shù)類(lèi)型、字典類(lèi)型和切片類(lèi)型的值并不支持判等操作,所以字典的鍵

類(lèi)型不能是這些類(lèi)型。

map映射過(guò)程

哈希表中查找與某個(gè)鍵值對(duì)應(yīng)的那個(gè)元素值,那么我們需要先把鍵值作為參數(shù)傳給這個(gè)哈希表。哈希表會(huì)先用哈希函數(shù)(hash function)把鍵值轉(zhuǎn)換為哈希值。哈希值通常是一個(gè)無(wú)符號(hào)的整數(shù)。一個(gè)哈希表會(huì)持有一定數(shù)量的桶(bucket),也可稱(chēng)之為哈希桶,這些哈希桶會(huì)均勻地儲(chǔ)存其所屬哈希表收納的那些鍵 - 元素對(duì)。因此,哈希表會(huì)先用這個(gè)鍵的哈希值的低幾位去定位到一個(gè)哈希桶,然后再去這個(gè)哈希桶中,查找這個(gè)鍵。由于鍵 - 元素對(duì)總是被捆綁在一起存儲(chǔ)的,所以一旦找到了鍵,就一定能找到對(duì)應(yīng)的元素值。隨后,哈希表就會(huì)把相應(yīng)的元素值作為結(jié)果返回。只要這個(gè)鍵 - 元素對(duì)存在于哈希表中就一定會(huì)被查找到。

如何判斷那些類(lèi)型作為字典的鍵比較合適?

在map查找的流程中,得知,“把鍵值轉(zhuǎn)換為哈希值”以及“把要查找的鍵值與哈希桶中的鍵值做對(duì)比”, 是明顯兩個(gè)重要且比較耗時(shí)的操作??梢哉f(shuō):**求哈希和判等操作的速度越快,對(duì)應(yīng)的類(lèi)型就越適合作為鍵類(lèi)型**。以求哈希的操作為例,寬度越小的類(lèi)型速度通常越快。

基本類(lèi)型:對(duì)于布爾類(lèi)型、整數(shù)類(lèi)型、浮點(diǎn)數(shù)類(lèi)型、復(fù)數(shù)類(lèi)型和指針類(lèi)型來(lái)說(shuō)都是如此。對(duì)于字符串類(lèi)型,由于它的寬度是不定的,所以要看它的值的具體長(zhǎng)度,長(zhǎng)度越短求哈希越快。類(lèi)型的寬度是指它的單個(gè)值需要占用的字節(jié)數(shù)。比如,bool、int8和uint8類(lèi)型的一個(gè)值需要占用的字節(jié)數(shù)都是1,因此這些類(lèi)型的寬度就都是1。

高級(jí)類(lèi)型。對(duì)數(shù)組類(lèi)型的值求哈希實(shí)際上是依次求得它的每個(gè)元素的哈希值并進(jìn)行合并,所以速度就取決于它的元素類(lèi)型以及它的長(zhǎng)度

為什么說(shuō)并發(fā)安全字典在盡量避免使用鎖?

我們從它的代碼來(lái)解釋

//sync.Map 包的結(jié)構(gòu)
type Map struct {
   mu Mutex //鎖
   
   /*
        由read字段的類(lèi)型可知,sync.Map在替換只讀字典的時(shí)候根本用不著鎖。另外,這個(gè)只讀字典
    在存儲(chǔ)鍵值對(duì)的時(shí)候,還在值之上封裝了一層。它先把值轉(zhuǎn)換為了unsafe.Pointer類(lèi)型的值,然后再把后者封     裝,并儲(chǔ)存在其中的原生字典中。如此一來(lái),在變更某個(gè)鍵所對(duì)應(yīng)的值的時(shí)候,就也可以使用原子操作了。
   */
   read atomic.Value// 只讀字典
   /*
        它存儲(chǔ)鍵值對(duì)的方式與read字段中的原生字典一致,它的鍵類(lèi)型也是interface{},并且同樣是把值先做   轉(zhuǎn)換和封裝后再進(jìn)行儲(chǔ)存的
   */
   dirty map[interface{}]*entry//臟字典。
   misses int//重建的判斷條件
}

查找鍵值對(duì)的時(shí)候:會(huì)先去只讀字典中尋找,并不需要鎖定互斥鎖。只有當(dāng)確定“只讀字典中沒(méi)有,但臟字典中可能會(huì)有這個(gè)鍵”的時(shí)候,它才會(huì)在鎖的保護(hù)下去訪問(wèn)臟字典。相對(duì)應(yīng)的,sync.Map在存儲(chǔ)鍵值對(duì)的時(shí)候,只要只讀字典中已存有這個(gè)鍵,并且該鍵值對(duì)未被標(biāo)記為“已刪除”,就會(huì)把新值存到里面并直接返回,這種情況下也不需要用到鎖。否則,它才會(huì)在鎖的保護(hù)下把鍵值對(duì)存儲(chǔ)到臟字典中。這個(gè)時(shí)候,該鍵值對(duì)的“已刪除”標(biāo)記會(huì)被
抹去。

當(dāng)一個(gè)鍵值對(duì)應(yīng)該被刪除,但卻仍然存在于只讀字典中的時(shí)候,才會(huì)被用標(biāo)記為“已刪除”的方式進(jìn)行邏輯刪除,而不會(huì)直接被物理刪除。

這種情況會(huì)在重建臟字典以后的一段時(shí)間內(nèi)出現(xiàn)。不過(guò),過(guò)不了多久,它們就會(huì)被真正刪除掉。
在查找和遍歷鍵值對(duì)的時(shí)候,已被邏輯刪除的鍵值對(duì)永遠(yuǎn)會(huì)被無(wú)視。

對(duì)于刪除鍵值對(duì),sync.Map會(huì)先去檢查只讀字典中是否有對(duì)應(yīng)的鍵。如果沒(méi)有,臟字典中可能有,那么它就會(huì)在鎖的保護(hù)下,試圖從臟字典中刪掉該鍵值對(duì)。最后,sync.Map會(huì)把該鍵值對(duì)中指向值的那個(gè)指針置為nil,這是另一種邏輯刪除的方式。

除此之外,還有一個(gè)細(xì)節(jié)需要注意,只讀字典和臟字典之間是會(huì)互相轉(zhuǎn)換的。在臟字典中查找鍵
值對(duì)次數(shù)足夠多的時(shí)候,sync.Map會(huì)把臟字典直接作為只讀字典,保存在它的read字段中,然
后把代表臟字典的dirty字段的值置為nil。

在讀操作有很多但寫(xiě)操作卻很少的情況下,并發(fā)安全字典的性能往往會(huì)更好。在幾個(gè)寫(xiě)操作當(dāng)中,新增鍵值對(duì)的操作對(duì)并發(fā)安全字典的性能影響是最大的,其次是刪除操作,最后才是修改操作。

如果被操作的鍵值對(duì)已經(jīng)存在于sync.Map的只讀字典中,并且沒(méi)有被邏輯刪除,那么修改它并
不會(huì)使用到鎖,對(duì)其性能的影響就會(huì)很小。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容