Go語言map不支持并發(fā)寫操作的原因
本文介紹 Go 語言為什么不支持并發(fā)讀寫 map?,Go 官方的說法是在多數(shù)情況下 map 只存在并發(fā)讀操作,如果原生支持并發(fā)讀寫,即降低了并發(fā)讀操作的性能。在使用 map 時(shí),要特別注意是否存在對 map 的并發(fā)寫操作,如果存在,要結(jié)合 sync 包的互斥鎖一起使用。
01 、介紹
在 Go 語言項(xiàng)目開發(fā)中,我們經(jīng)常會使用哈希表 map,它的時(shí)間復(fù)雜度是 O(1),Go 語言中的 map 使用開放尋址法避免哈希碰撞。
Go 語言中的 map 并非原子操作,不支持并發(fā)讀寫操作。
Go 官方認(rèn)為 map 在大多數(shù)情況下是使用 map 進(jìn)行并發(fā)讀操作,僅在少數(shù)情況下是使用 map 進(jìn)行并發(fā)讀寫操作。
如果 Go 語言中的 map 原生支持并發(fā)讀寫操作,在操作時(shí)需要先獲取互斥鎖,反而會降低只有并發(fā)讀操作時(shí)的性能。
在需要并發(fā)讀寫操作 map 時(shí),可以結(jié)合 sync 包中的互斥鎖一起使用。
02 、并發(fā)讀寫 map
Go 支持并發(fā)讀 map,不支持并發(fā)讀寫 map。
示例代碼:
func main() {
var m = make(map[int]string)
go func() {
for {
m[1] = "xx"
}
}()
go func() {
for {
_ = m[1]
}
}()
time.Sleep(time.Second * 3)
}輸出結(jié)果:
fatal error: concurrent map read and map write // ...
閱讀上面這段代碼,我們并發(fā)讀寫 map 類型的變量 m,在運(yùn)行時(shí),返回致命錯(cuò)誤 fatal error: concurrent map read and map write。
Go 語言中的 map 在運(yùn)行時(shí)是怎么檢測到 map 的存在寫操作?
源碼:
const (
// flags
iterator = 1 // there may be an iterator using buckets
oldIterator = 2 // there may be an iterator using oldbuckets
hashWriting = 4 // a goroutine is writing to the map
sameSizeGrow = 8 // the current map growth is to a new map of the same size
)
// A header for a Go map.
type hmap struct {
count int // # live cells == size of map. Must be first (used by len() builtin)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
hash0 uint32 // hash seed
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // optional fields
}
// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
// ...
done:
if h.flags&hashWriting == 0 {
fatal("concurrent map writes")
}
h.flags &^= hashWriting
if t.IndirectElem() {
elem = *((*unsafe.Pointer)(elem))
}
return elem
}閱讀上面這段源碼,我們可以發(fā)現(xiàn)在 hmap 結(jié)構(gòu)體中的字段 flags,該字段用于標(biāo)記 map 是否為寫入狀態(tài)。
在訪問 map 時(shí),通過判斷 hmap.flags 和 hashWriting 的值,可知是否有其它 goroutine 訪問 map,如果有,則返回致命錯(cuò)誤 fatal("concurrent map writes")。
03 、總結(jié)
本文介紹 Go 語言為什么不支持并發(fā)讀寫 map,Go 官方的說法是在多數(shù)情況下 map 只存在并發(fā)讀操作,如果原生支持并發(fā)讀寫,即降低了并發(fā)讀操作的性能。
通過閱讀源碼,我們了解到在運(yùn)行時(shí)檢測是否存在其它 goroutine 對 map 的寫操作,如果存在,則返回致命錯(cuò)誤。
讀者朋友們在使用 map 時(shí),要特別注意是否存在對 map 的并發(fā)寫操作,如果存在,要結(jié)合 sync 包的互斥鎖一起使用。
到此這篇關(guān)于Go語言map不支持并發(fā)寫操作的原因的文章就介紹到這了,更多相關(guān)Go中map不支持并發(fā)寫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言中http和mysql的實(shí)現(xiàn)代碼
本文通過實(shí)例代碼給大家介紹了Go語言中http和mysql的實(shí)現(xiàn)代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11
GoLang bytes.Buffer基礎(chǔ)使用方法詳解
Go標(biāo)準(zhǔn)庫中的bytes.Buffer(下文用Buffer表示)類似于一個(gè)FIFO的隊(duì)列,它是一個(gè)流式字節(jié)緩沖區(qū),我們可以持續(xù)向Buffer尾部寫入數(shù)據(jù),從Buffer頭部讀取數(shù)據(jù)。當(dāng)Buffer內(nèi)部空間不足以滿足寫入數(shù)據(jù)的大小時(shí),會自動擴(kuò)容2023-03-03
Golang中 import cycle not allowed 問題
這篇文章主要介紹了Golang中 import cycle not allowed 問題的解決方法,問題從描述到解決都非常詳細(xì),需要的小伙伴可以參考一下2022-03-03
使用golang生成prometheus格式數(shù)據(jù)
Prometheus是一個(gè)開源的監(jiān)控系統(tǒng),擁有許多Advanced?Feature,本文將介紹Primetheus?client的使用,并基于golang生成prometheus格式數(shù)據(jù),希望對大家有所幫助2025-02-02

