golang中sync.map的實現
阿新 • • 發佈:2021-07-28
sync.map
適用於讀多寫少的場景。對於寫多的場景,會導致 read map 快取失效,需要加鎖,導致衝突變多;而且由於未命中 read map 次數過多,導致 dirty map 提升為 read map,這是一個 O(N) 的操作,會進一步降低效能。
type Map struct {
//當涉及到dirty資料的操作的時候,需要使用這個鎖
mu Mutex
// 一個只讀的資料結構,因為只讀,所以不會有讀寫衝突。
// 所以從這個資料中讀取總是安全的。
// 實際上,實際也會更新這個資料的entries,如果entry是未刪除的(unexpunged), 並不需要加鎖。如果entry已經被刪除了,需要加鎖,以便更新dirty資料。
read atomic.Value // readOnly
// dirty資料包含當前的map包含的entries,它包含最新的entries(包括read中未刪除的資料,雖有冗餘,但是提升dirty欄位為read的時候非常快,
不用一個一個的複製,而是直接將這個資料結構作為read欄位的一部分),有些資料還可能沒有移動到read欄位中。
// 對於dirty的操作哦需要加鎖,因為對它的操作可能會有讀寫競爭。
// 當dirty為空的時候, 比如初始化或者剛提升完,下一次的寫操作會複製read欄位中未刪除的資料到這個資料中。
dirty map[interface{}]*entry
// 當從Map中讀取entry的時候,如果read中不包含這個entry,會嘗試從dirty中讀取,這個時候會將misses加一,
// 當misses累積到 dirty的長度的時候, 就會將dirty提升為read,避免從dirty中miss太多次。因為操作dirty需要加鎖。
misses int
}
互斥量 mu
保護 read 和 dirty。
read
是 atomic.Value 型別,可以併發地讀。但如果需要更新 read
,則需要加鎖保護。對於 read 中儲存的 entry 欄位,可能會被併發地 CAS 更新。但是如果要更新一個之前已被刪除的 entry,則需要先將其狀態從 expunged 改為 nil,再拷貝到 dirty 中,然後再更新。
dirty
是一個非執行緒安全的原始 map。包含新寫入的 key,並且包含 read
中的所有未被刪除的 key。這樣,可以快速地將 dirty
提升為 read
對外提供服務。如果 dirty
為 nil,那麼下一次寫入時,會新建一個新的 dirty
,這個初始的 dirty
是 read
的一個拷貝,但除掉了其中已被刪除的 key。
每當從 read 中讀取失敗,都會將 misses
的計數值加 1,當加到一定閾值以後,需要將 dirty 提升為 read,以期減少 miss 的情形。
type readOnly struct {
m map[interface{}]*entry
amended bool // 如果Map.dirty有些資料不在中的時候,這個值為true
}
都是先從操作m.read開始的,不滿足條件再加鎖,然後操作m.dirty。