Go實(shí)現(xiàn)map并發(fā)安全的3種方式總結(jié)
實(shí)現(xiàn)map并發(fā)讀寫(xiě)線程安全
1. 加鎖
對(duì)整個(gè)map加上讀寫(xiě)鎖sync.RWMutex
//keyType為key的類型,valueType為value的類型 type RWMap struct { Map map[keyType]valueType sync.RWMutex } func NewRWMap(capacity int) *RWMap { if capacity < 0 { capacity = 0 } return &RWMap{ Map: make(map[keyType]valueType, capacity), } } //add or update func (m *RWMap) Set(key keyType, value valueType) { m.Lock() defer m.Unlock() m.Map[key] = value } //delete func (m *RWMap) Delete(key int) { m.Lock() defer m.Unlock() delete(m.Map, key) } //get func (m *RWMap) Get(key int) valueType { m.RLock() defer m.RUnlock() return m.Map[key] }
優(yōu)點(diǎn):解決了問(wèn)題。
缺點(diǎn):鎖粒度大。
2. 分片加鎖
一個(gè)操作會(huì)導(dǎo)致整個(gè)map被鎖住,導(dǎo)致性能降低。所以提出了分片思想,將一個(gè)map分成幾個(gè)片,按片加鎖。
第三方包實(shí)現(xiàn):github.com/orcaman/concurrent-map
github上用 map language:go 搜索:
3.4kstar
插曲:注意,如果你的goland ide 版本太老的話,github.com/orcaman/concurrent-map/v2 版本是用不了的:
所以我最后換成VSCode,發(fā)現(xiàn)就沒(méi)這個(gè)問(wèn)題了。(因?yàn)樾掳姹镜腉oLand還得繼續(xù)想法子破解)
源碼New方法返回的map,看到key只支持string
// Creates a new concurrent map. func New[V any]() ConcurrentMap[string, V] { return create[string, V](fnv32) }
Example and usage
package main import ( "fmt" "time" cmap "github.com/orcaman/concurrent-map/v2" ) func main() { m := cmap.New[int]() for i := 0; i < 300; i++ { go func(i int) { m.Set(fmt.Sprintf("%v", i), i*2) //并發(fā)寫(xiě) }(i) } time.Sleep(4 * time.Second) fmt.Println(len(m.Keys())) }
execute and output:
PS C:\GoWork\src\asset-manager\mytest> go run main.go 300
并發(fā)寫(xiě)沒(méi)問(wèn)題。
更多使用示例包里的concurrent_map_test.go里面提供了。
3. sync.Map
標(biāo)準(zhǔn)庫(kù)中的 sync.Map是專為 append-only 場(chǎng)景設(shè)計(jì)的。
sync.Map在讀多寫(xiě)少性能比較好,否則并發(fā)性能很差。
Go源碼:
// Map is like a Go map[interface{}]interface{} but is safe for concurrent use // by multiple goroutines without additional locking or coordination. // Loads, stores, and deletes run in amortized constant time. //=====自注釋====== sync.Map 很像Go map[interface{}]interface{}。但sync.Map是線程安全的,能被多個(gè)協(xié)程在沒(méi)有額外的鎖或者協(xié)調(diào)的情況下并發(fā)使用。 Loads,stores,deletes操作都運(yùn)行在分?jǐn)偝?shù)時(shí)間內(nèi)。 amortized 平攤的(adj.)英 /??m??ta?zd/ //=====自注釋====== // // The Map type is specialized. Most code should use a plain Go map instead, // with separate locking or coordination, for better type safety and to make it // easier to maintain other invariants along with the map content. //=====自注釋====== invariants (n.) 不變量(invariant的復(fù)數(shù))/?n?veri?nts/ sync.Map 類型是為特殊情況專門(mén)設(shè)計(jì)的。 大多數(shù)代碼都應(yīng)該使用普通的Go map + 單獨(dú)的鎖或者協(xié)調(diào) ,這種形式,來(lái)獲得更好的類型安全 以及使得在維護(hù)映射內(nèi)容的同時(shí)維護(hù)其他不變量更容易。 //=====自注釋====== // // The Map type is optimized for two common use cases: (1) when the entry for a given // key is only ever written once but read many times, as in caches that only grow, // or (2) when multiple goroutines read, write, and overwrite entries for disjoint // sets of keys. In these two cases, use of a Map may significantly reduce lock // contention compared to a Go map paired with a separate Mutex or RWMutex. //=====自注釋====== disjoint (adj.) 不連貫的,(兩個(gè)集合)不相交的 /d?s?d???nt/ sync.Map類型針對(duì)兩個(gè)常見(jiàn)用例進(jìn)行了優(yōu)化: (1)對(duì)于一個(gè)給定的key,只會(huì)寫(xiě)一次,但是讀很多次,就像在只增長(zhǎng)的緩存中一樣。 (2)當(dāng)多個(gè)協(xié)程讀,寫(xiě),重寫(xiě)不相交的keys。 以上兩種情況,相比于使用Go map + Mutex(或者RWMutex),使用sync.Map能顯著減少鎖競(jìng)爭(zhēng)。 //=====自注釋====== // // The zero Map is empty and ready for use. A Map must not be copied after first use. type Map struct { mu Mutex // read contains the portion of the map's contents that are safe for // concurrent access (with or without mu held). // // The read field itself is always safe to load, but must only be stored with // mu held. // // Entries stored in read may be updated concurrently without mu, but updating // a previously-expunged entry requires that the entry be copied to the dirty // map and unexpunged with mu held. read atomic.Value // readOnly // dirty contains the portion of the map's contents that require mu to be // held. To ensure that the dirty map can be promoted to the read map quickly, // it also includes all of the non-expunged entries in the read map. //=====自注釋====== expunged (adj.)/?k?sp?nd?/ 被擦去的,被刪掉的 dirty map 包含map內(nèi)容的部分,該部分要求持有mu鎖。為了確保dirty map能快速提升到read map, 它還包括read map 中所有未刪除的項(xiàng)。 //=====自注釋====== // // Expunged entries are not stored in the dirty map. An expunged entry in the // clean map must be unexpunged and added to the dirty map before a new value // can be stored to it. // // If the dirty map is nil, the next write to the map will initialize it by // making a shallow copy of the clean map, omitting stale entries. dirty map[any]*entry // misses counts the number of loads since the read map was last updated that // needed to lock mu to determine whether the key was present. // // Once enough misses have occurred to cover the cost of copying the dirty // map, the dirty map will be promoted to the read map (in the unamended // state) and the next store to the map will make a new dirty copy. misses int }
read atomic.Value
sync/stomic包里都是go提供的原子操作。
sync.Map思想:就是用兩個(gè)數(shù)據(jù)結(jié)構(gòu)(只讀的 read 和可寫(xiě)的 dirty)盡量將讀寫(xiě)操作分開(kāi),并最小粒度加鎖,來(lái)減少鎖對(duì)性能的影響。
總結(jié)
較常使用的是前兩種:加讀寫(xiě)鎖和分片加鎖。特定場(chǎng)景下sync.Map性能會(huì)有更優(yōu)的表現(xiàn)(要滿足那兩個(gè)場(chǎng)景條件比較苛刻,實(shí)際很少用)。
相關(guān)文章
Go語(yǔ)言中實(shí)現(xiàn)Unix風(fēng)格的進(jìn)程管道方法實(shí)例
這篇文章主要為大家介紹了Go語(yǔ)言中實(shí)現(xiàn)Unix風(fēng)格的進(jìn)程管道方法實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Golang Cron 定時(shí)任務(wù)的實(shí)現(xiàn)示例
這篇文章主要介紹了Golang Cron 定時(shí)任務(wù)的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05使用Go語(yǔ)言開(kāi)發(fā)自動(dòng)化API測(cè)試工具詳解
這篇文章主要為大家詳細(xì)介紹了如何使用Go語(yǔ)言開(kāi)發(fā)自動(dòng)化API測(cè)試工具,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2024-03-03golang并發(fā)編程中Goroutine 協(xié)程的實(shí)現(xiàn)
Go語(yǔ)言中的協(xié)程是一種輕量級(jí)線程,通過(guò)在函數(shù)前加go關(guān)鍵字來(lái)并發(fā)執(zhí)行,具有動(dòng)態(tài)棧、快速啟動(dòng)和低內(nèi)存使用等特點(diǎn),本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2024-10-10