這篇文章給大家介紹 Golang中sync.Map的實(shí)現(xiàn)原理是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
成都創(chuàng)新互聯(lián)公司是專(zhuān)業(yè)的濮陽(yáng)網(wǎng)站建設(shè)公司,濮陽(yáng)接單;提供成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、外貿(mào)網(wǎng)站建設(shè),網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專(zhuān)業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行濮陽(yáng)網(wǎng)站開(kāi)發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專(zhuān)業(yè)做搜索引擎喜愛(ài)的網(wǎng)站,專(zhuān)業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來(lái)合作!
Go 的內(nèi)建 map
是不支持并發(fā)寫(xiě)操作的,原因是 map
寫(xiě)操作不是并發(fā)安全的,當(dāng)你嘗試多個(gè) Goroutine 操作同一個(gè) map
,會(huì)產(chǎn)生報(bào)錯(cuò):fatal error: concurrent map writes
。
因此官方另外引入了 sync.Map
來(lái)滿足并發(fā)編程中的應(yīng)用。
sync.Map
的實(shí)現(xiàn)原理可概括為:
通過(guò) read 和 dirty 兩個(gè)字段將讀寫(xiě)分離,讀的數(shù)據(jù)存在只讀字段 read 上,將最新寫(xiě)入的數(shù)據(jù)則存在 dirty 字段上
讀取時(shí)會(huì)先查詢 read,不存在再查詢 dirty,寫(xiě)入時(shí)則只寫(xiě)入 dirty
讀取 read 并不需要加鎖,而讀或?qū)?dirty 都需要加鎖
另外有 misses 字段來(lái)統(tǒng)計(jì) read 被穿透的次數(shù)(被穿透指需要讀 dirty 的情況),超過(guò)一定次數(shù)則將 dirty 數(shù)據(jù)同步到 read 上
對(duì)于刪除數(shù)據(jù)則直接通過(guò)標(biāo)記來(lái)延遲刪除
Map
的數(shù)據(jù)結(jié)構(gòu)如下:
type Map struct { // 加鎖作用,保護(hù) dirty 字段 mu Mutex // 只讀的數(shù)據(jù),實(shí)際數(shù)據(jù)類(lèi)型為 readOnly read atomic.Value // 最新寫(xiě)入的數(shù)據(jù) dirty map[interface{}]*entry // 計(jì)數(shù)器,每次需要讀 dirty 則 +1 misses int }
其中 readOnly 的數(shù)據(jù)結(jié)構(gòu)為:
type readOnly struct { // 內(nèi)建 map m map[interface{}]*entry // 表示 dirty 里存在 read 里沒(méi)有的 key,通過(guò)該字段決定是否加鎖讀 dirty amended bool }
entry
數(shù)據(jù)結(jié)構(gòu)則用于存儲(chǔ)值的指針:
type entry struct { p unsafe.Pointer // 等同于 *interface{} }
屬性 p 有三種狀態(tài):
p == nil
: 鍵值已經(jīng)被刪除,且 m.dirty == nil
p == expunged
: 鍵值已經(jīng)被刪除,但 m.dirty!=nil
且 m.dirty
不存在該鍵值(expunged 實(shí)際是空接口指針)
除以上情況,則鍵值對(duì)存在,存在于 m.read.m
中,如果 m.dirty!=nil
則也存在于 m.dirty
Map
常用的有以下方法:
Load
:讀取指定 key 返回 value
Store
: 存儲(chǔ)(增或改)key-value
Delete
: 刪除指定 key
func (m *Map) Load(key interface{}) (value interface{}, ok bool) { // 首先嘗試從 read 中讀取 readOnly 對(duì)象 read, _ := m.read.Load().(readOnly) e, ok := read.m[key] // 如果不存在則嘗試從 dirty 中獲取 if !ok && read.amended { m.mu.Lock() // 由于上面 read 獲取沒(méi)有加鎖,為了安全再檢查一次 read, _ = m.read.Load().(readOnly) e, ok = read.m[key] // 確實(shí)不存在則從 dirty 獲取 if !ok && read.amended { e, ok = m.dirty[key] // 調(diào)用 miss 的邏輯 m.missLocked() } m.mu.Unlock() } if !ok { return nil, false } // 從 entry.p 讀取值 return e.load() } func (m *Map) missLocked() { m.misses++ if m.misses < len(m.dirty) { return } // 當(dāng) miss 積累過(guò)多,會(huì)將 dirty 存入 read,然后 將 amended = false,且 m.dirty = nil m.read.Store(readOnly{m: m.dirty}) m.dirty = nil m.misses = 0 }
func (m *Map) Store(key, value interface{}) { read, _ := m.read.Load().(readOnly) // 如果 read 里存在,則嘗試存到 entry 里 if e, ok := read.m[key]; ok && e.tryStore(&value) { return } // 如果上一步?jīng)]執(zhí)行成功,則要分情況處理 m.mu.Lock() read, _ = m.read.Load().(readOnly) // 和 Load 一樣,重新從 read 獲取一次 if e, ok := read.m[key]; ok { // 情況 1:read 里存在 if e.unexpungeLocked() { // 如果 p == expunged,則需要先將 entry 賦值給 dirty(因?yàn)?nbsp;expunged 數(shù)據(jù)不會(huì)留在 dirty) m.dirty[key] = e } // 用值更新 entry e.storeLocked(&value) } else if e, ok := m.dirty[key]; ok { // 情況 2:read 里不存在,但 dirty 里存在,則用值更新 entry e.storeLocked(&value) } else { // 情況 3:read 和 dirty 里都不存在 if !read.amended { // 如果 amended == false,則調(diào)用 dirtyLocked 將 read 拷貝到 dirty(除了被標(biāo)記刪除的數(shù)據(jù)) m.dirtyLocked() // 然后將 amended 改為 true m.read.Store(readOnly{m: read.m, amended: true}) } // 將新的鍵值存入 dirty m.dirty[key] = newEntry(value) } m.mu.Unlock() } func (e *entry) tryStore(i *interface{}) bool { for { p := atomic.LoadPointer(&e.p) if p == expunged { return false } if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) { return true } } } func (e *entry) unexpungeLocked() (wasExpunged bool) { return atomic.CompareAndSwapPointer(&e.p, expunged, nil) } func (e *entry) storeLocked(i *interface{}) { atomic.StorePointer(&e.p, unsafe.Pointer(i)) } func (m *Map) dirtyLocked() { if m.dirty != nil { return } read, _ := m.read.Load().(readOnly) m.dirty = make(map[interface{}]*entry, len(read.m)) for k, e := range read.m { // 判斷 entry 是否被刪除,否則就存到 dirty 中 if !e.tryExpungeLocked() { m.dirty[k] = e } } } func (e *entry) tryExpungeLocked() (isExpunged bool) { p := atomic.LoadPointer(&e.p) for p == nil { // 如果有 p == nil(即鍵值對(duì)被 delete),則會(huì)在這個(gè)時(shí)機(jī)被置為 expunged if atomic.CompareAndSwapPointer(&e.p, nil, expunged) { return true } p = atomic.LoadPointer(&e.p) } return p == expunged }
func (m *Map) Delete(key interface{}) { m.LoadAndDelete(key) } // LoadAndDelete 作用等同于 Delete,并且會(huì)返回值與是否存在 func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) { // 獲取邏輯和 Load 類(lèi)似,read 不存在則查詢 dirty read, _ := m.read.Load().(readOnly) e, ok := read.m[key] if !ok && read.amended { m.mu.Lock() read, _ = m.read.Load().(readOnly) e, ok = read.m[key] if !ok && read.amended { e, ok = m.dirty[key] m.missLocked() } m.mu.Unlock() } // 查詢到 entry 后執(zhí)行刪除 if ok { // 將 entry.p 標(biāo)記為 nil,數(shù)據(jù)并沒(méi)有實(shí)際刪除 // 真正刪除數(shù)據(jù)并被被置為 expunged,是在 Store 的 tryExpungeLocked 中 return e.delete() } return nil, false }
可見(jiàn),通過(guò)這種讀寫(xiě)分離的設(shè)計(jì),解決了并發(fā)情況的寫(xiě)入安全,又使讀取速度在大部分情況可以接近內(nèi)建 map
,非常適合讀多寫(xiě)少的情況。
sync.Map
還有一些其他方法:
Range
:遍歷所有鍵值對(duì),參數(shù)是回調(diào)函數(shù)
LoadOrStore
:讀取數(shù)據(jù),若不存在則保存再讀取
關(guān)于 Golang中sync.Map的實(shí)現(xiàn)原理是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。