golang 裡面 map 不是併發安全的,這一點是眾所周知的,而且官方文件也很早就給了解釋:why are map operations not defined to be atomic?. 也正如這個解釋說的一樣,要實現乙個併發安全的 map 其實非常簡單。
實際上,大多數情況下,對乙個 map 的訪問都是讀操作多於寫操作,而且讀的時候,是可以共享的。所以這種場景下,用乙個sync.rwmutex
保護一下就是很好的選擇:
type syncmap struct
sync.rwmutex
}
上面這個結構體定義了乙個併發安全的 string map,用乙個 map 來儲存資料,乙個讀寫鎖來保護安全。這個 map 可以被任意多的 goroutine 同時讀,但是寫的時候,會阻塞其他讀寫操作。新增上get
,set
,delete
等方法,這個設計是能夠工作的,而且大多數時候能表現不錯。
但是這種設計會有些效能隱患。主要是兩個方面:
讀寫鎖的粒度太大了,保護了整個 map 的訪問。寫操作是阻塞的,此時其他任何讀操作都無法進行。
如果內部的 map 儲存了很多 key,gc 的時候就需要掃瞄很久。
一種解決思路是「分表」儲存,具體實現就是,基於上面的syncmap
再包裝一次,用多個syncmap
來模擬實現乙個 map:
type syncmap struct
上面這種設計用了乙個*syncmap
的 slice 來儲存資料,shardcount
提供了分表量的可定製性。實際上shards
同樣可以實現為map[string]*syncmap
。
在這種設計下,資料(key:value)會被分散到不同的syncmap
,而每個syncmap
又有自己底層的 map。資料分散了,鎖也分散了,能夠很大程度上提高隨機訪問效能。而且在資料量大、高併發、寫操作頻繁的場景下,這種提公升會更加明顯。
那麼資料如何被分配到指定的分塊呢?一種很通用也很簡單的方法就是 hash. 字串的雜湊演算法有很多,byvoid 大神實現和比較了多種字串 hash 函式(各種字串hash函式比較),得出結論是:「bkdrhash無論是在實際效果還是編碼實現中,效果都是最突出的」。這裡採用了 bkdrhash 來實現:
const seed uint32 = 131 // 31 131 1313 13131 131313 etc..
func bkdrhash(str string) uint32
return h
}// find the specific shard with the given key
func (m *syncmap) locate(key string) *syncmap
locate
方法呼叫bkdrhash
函式計算乙個key
的雜湊值,然後用該值對分表量取模得到在 slice 的index
,之後就能定位到對應的syncmap
.
這種實現足夠簡單,而且也有不錯的效能表現。除了基本的get
、set
、delete
等基本操作之外,迭代(range
)功能也非常有用。更多的功能和細節,都可以在原始碼裡找到答案:
還有一點:
如果業務場景能保證我們絕不會同時讀寫乙個key的話也不用加鎖,乙個小例子試驗下。。
會有衝突的情況:
package main
func main()
}func readmap(map map[int]int, key int) int
func writemap(map map[int]int, key int, value int)
go run -race main.go 結果如下:
那麼稍微改下呢。。
package main
func main()
}func readmap(map map[int]int, key int) int
func writemap(map map[int]int, key int, value int)
出現的是不衝突,但是沒啥意義,因為幾乎沒有這樣的應用場景吧,在這做這個小測試只是想說明golang 的map會出現讀寫衝突是因為map是引用型別,同時讀寫共享資源會使得共享資源崩潰 Go語言併發程式設計 讀寫鎖
通過對互斥鎖的學習,我們已經了解了鎖的概念及用途。主要用於處理併發中的臨界資源問題。rwmutex是基於mutex實現的,唯讀鎖的實現使用類似引用計數器的功能。rwmutext是讀 寫互斥鎖。鎖可以由任意數量的讀取器或單個編寫器持有。rwmutex的零值是未鎖定的mutex。當有乙個goroutin...
10 筆記go語言 併發
10.筆記go語言 併發 goroutine 是由 go 執行時環境管理的輕量級執行緒。go f x,y,z 開啟乙個新的 goroutine 執行 f x,y,z f x y 和 z 是當前 goroutine中定義的,但是在新的 goroutine 中執行 f goroutine 在相同的位址空...
go學習 goroutine併發學習總結
go最大的特性就是併發了,所以這一塊是go的重點知識,我自己花了一些時間,好好學習這個基礎知識。文章內容為個人學習理解,所以文章如果有不對之處,非常感謝指出。說起go併發,一般都會指go協程,即goroutine,而實現goroutin的關鍵字就是go。我學習go併發用到的關鍵字總結 sync.mu...