欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang?sync.Map底層實(shí)現(xiàn)場景示例詳解

 更新時(shí)間:2023年09月07日 09:50:04   作者:EricLee  
這篇文章主要為大家介紹了Golang?sync.Map底層實(shí)現(xiàn)及使用場景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

Go中普通的map是非線程安全的,想要線程安全的訪問一個(gè)map,有兩種方式一種是map+mutex另一種就是原生的sync.Map,這篇文章會詳細(xì)的介紹sync.Map底層是如何實(shí)現(xiàn)的,以及一些常用的場景。

如何保證線程安全?

sync.Map的數(shù)據(jù)結(jié)構(gòu)如下:

type Map struct {
   mu Mutex // 鎖,保證寫操作及dirty晉升為read的線程安全
   read atomic.Value // readOnly 只讀map
   dirty map[any]*entry // 臟map,當(dāng)內(nèi)部有數(shù)據(jù)時(shí)就一定包含read中的數(shù)據(jù)
   misses int // read未命中次數(shù),當(dāng)達(dá)到一定次數(shù)時(shí)會觸發(fā)dirty中的護(hù)具晉升到read
}

如果只看這個(gè)結(jié)構(gòu)我們可能會有以下幾個(gè)疑問:

  • sync.Map中也用了mutex那和map+mutex的實(shí)現(xiàn)方式不就一樣了嗎?
  • misses做什么用的?
  • read的類型是一個(gè)atomic.Value而dirty是map[any]*entry,為什么不同?

sync.Map中也用了mutex那和map+mutex的實(shí)現(xiàn)方式不就一樣了嗎?

  • 在本質(zhì)上都是通過map+mutex的實(shí)現(xiàn)方式來實(shí)現(xiàn)的
  • sync.Map通過增加read map,降低在進(jìn)行讀取操作時(shí)的加鎖概率,增加讀取的性能。

misses做什么用的?

  • misses是用于標(biāo)記read中未命中次數(shù)的
  • 當(dāng)misses達(dá)到一定值時(shí)會觸發(fā)dirty的晉升(晉升為read)

具體源碼如下:

// 當(dāng)執(zhí)行Load操作時(shí)沒能在read中命中key,則進(jìn)行一次miss記錄
func (m *Map) missLocked() {
   // 1.miss計(jì)數(shù)加1
   m.misses++
   // 2.判斷dirty是否滿足晉升條件
   if m.misses < len(m.dirty) {
      // 2.1不滿足直接返回
      return
   }
   // 3.將dirty中的數(shù)據(jù)轉(zhuǎn)存到read的m中,舊的read中的數(shù)據(jù)被拋棄
   m.read.Store(readOnly{m: m.dirty})
   // 4.清空dirty
   m.dirty = nil
   // 5.重置miss計(jì)數(shù)
   m.misses = 0
}

從代碼中可以看到:

  • 當(dāng)misses值大于等于dirty中數(shù)據(jù)個(gè)數(shù)的時(shí)候會觸發(fā)dirty的晉升
  • 在dirty晉升時(shí),直直接把read重置成了一個(gè)新生成的readOnly,其中m為新的dirty,amended為默認(rèn)值false,保證每次觸發(fā)晉升都自動將amended設(shè)置為了false
  • 在dirty晉升時(shí),并沒有觸發(fā)數(shù)據(jù)的拷貝

read的類型是一個(gè)atomic.Value而dirty是map[any]*entry,為什么不同?

type readOnly struct {
   m       map[any]*entry // read map中的數(shù)據(jù)
   amended bool // 標(biāo)記dirty map中是否有read中沒有的key,如果有,則此值為true
}
type entry struct {
   p unsafe.Pointer // *interface{}  一個(gè)指向具體數(shù)據(jù)的指針
}
  • read的類型底層是存儲的readOnly類型,而readOnly類型只是在map[any]*entry的基礎(chǔ)上增加了一個(gè)amended標(biāo)記
  • 如果amended為false,則代表dirty中沒有read中沒有的數(shù)據(jù),此時(shí)可以避免一次dirty操作(會加鎖),從而降低無意義的加鎖。
  • read被聲明為atomic.Value類型是為了滿足在無鎖的情況下多個(gè)Goroutine同時(shí)讀取read時(shí)的數(shù)據(jù)一致性

sync.Map適用于那些場景?

sync.Map更適合讀多寫少的場景,而當(dāng)map需要頻繁寫入的時(shí)候,map+mutex的方案通過控制鎖的力度可以達(dá)到比sync.Map更好的性能。

sync.Map不支持遍歷操作,因?yàn)樽x寫分離的設(shè)計(jì)使得在遍歷過程中可能存在一些未完成的修改操作,導(dǎo)致遍歷結(jié)果不確定。

為什么sync.Map適合讀多寫少的場景?

sync.Map的讀取方法為Load方法,具體的源碼實(shí)現(xiàn)如下:

func (m *Map) Load(key any) (value any, ok bool) {
   // 1.將read中的數(shù)據(jù)強(qiáng)轉(zhuǎn)為readOnly
   read, _ := m.read.Load().(readOnly)
   // 2.從read中查詢key,檢查數(shù)據(jù)是否存在
   e, ok := read.m[key]
   // 3.如果read中不存在,且amended標(biāo)記顯示dirty中存在read中沒有的key,則去dirty中查詢
   if !ok && read.amended {
      // 3.1開始操作dirty,需要加鎖保證線程安全
      m.mu.Lock()
      // 3.2 重新從read中檢查一次,避免在Lock執(zhí)行前dirty中的數(shù)據(jù)觸發(fā)了晉升到read的操作
      read, _ = m.read.Load().(readOnly)
      e, ok = read.m[key]
      // 3.3 同3
      if !ok && read.amended {
         // 3.4 從dirty中查詢
         e, ok = m.dirty[key]
         // 3.5 無論是否從dirty中查詢到數(shù)據(jù),都相當(dāng)于從read中miss了,需要更新miss計(jì)數(shù)(更新計(jì)數(shù)可能會觸發(fā)dirty數(shù)據(jù)的晉升)
         m.missLocked()
      }
      // 3.5 操作完成解鎖
      m.mu.Unlock()
   }
   // 4.檢測結(jié)果,ok為false代表沒有查詢到數(shù)據(jù)
   // ok為true分為兩種情況:1.從read中查詢到了數(shù)據(jù),read命中;2.從dirty中查詢到了數(shù)據(jù)
   // ok為false分為兩種情況:
   //    1.read沒有命中,但read.amended為false
   //    2.read沒有命中,read.amended為true,但dirty中也不存在
   if !ok {
      return nil, false
   }
   // 返回查詢到的數(shù)據(jù)(這個(gè)值也并不一定是真的存在,需要根據(jù)p確定是一個(gè)正常的值還是一個(gè)nil)
   return e.load()
}
// 當(dāng)從read或者dirty中獲取到一個(gè)key的值的指針時(shí),需要去加載對應(yīng)指針的值
func (e *entry) load() (value any, ok bool) {    
   // 1.院子操作獲取對應(yīng)地址的值
   p := atomic.LoadPointer(&e.p)
   // 2.如果值已經(jīng)不存在或者標(biāo)記為被刪除則返回nil,false
   if p == nil || p == expunged {
      return nil, false
   }
   // 3.返回具體的值,true
   return *(*any)(p), true
}

每次讀取數(shù)據(jù)時(shí),優(yōu)先從read中讀取,且read中數(shù)據(jù)的讀取不需要進(jìn)行加鎖操作;當(dāng)read中未命中且amended標(biāo)記顯示dirty中存在read中沒有的數(shù)據(jù)時(shí),才進(jìn)行dirty查詢,并加鎖。在讀多寫少的情況下,大多數(shù)時(shí)候數(shù)據(jù)都在read中所以可以避免加鎖,以此來提高并發(fā)讀的性能。

sync.Map的寫操作方法為Store方法

func (m *Map) Store(key, value any) {
   // 1.從read中查詢數(shù)據(jù)是否已經(jīng)存在,如果存在則嘗試修改
   read, _ := m.read.Load().(readOnly)
   if e, ok := read.m[key]; ok && e.tryStore(&value) {
      // 1.1 read中存在數(shù)據(jù),且更新完成,直接返回
      return
   }
   //2.read中沒有,準(zhǔn)備操作dirty,為保證線程安全加鎖
   m.mu.Lock()
   read, _ = m.read.Load().(readOnly)
   if e, ok := read.m[key]; ok {
   // 3.如果在read中查詢到數(shù)據(jù),則檢查是否已經(jīng)被標(biāo)記為刪除,
      if e.unexpungeLocked() {
      // 3.1 如果被標(biāo)記為刪除需要清空標(biāo)記并加入到dirty中
         m.dirty[key] = e
      }
      // 3.2 更新entry的值
      e.storeLocked(&value)
   } else if e, ok := m.dirty[key]; ok {
      // 4 如果存在于dirty中,則直接更新entry的值
      e.storeLocked(&value)
   } else {
   // 5 如果之前這個(gè)key不存在,則將新的key-value加入到dirty中
      if !read.amended {
      // 5.1 如果read.amended標(biāo)記顯示之前dirty中不存在read中沒有的key,則重置dirty,并標(biāo)amended為true
         // 5.1.1 將read中所有未被標(biāo)記為刪除的entry重新加入到dirty中
         m.dirtyLocked()
         // 5.1.2 更新read.amended標(biāo)記
         m.read.Store(readOnly{m: read.m, amended: true})
      }
      // 5.2 將新的key-value加入到dirty中
      m.dirty[key] = newEntry(value)
   }
   m.mu.Unlock()
}
// 判斷entry是否被標(biāo)記為刪除,如果是將其修改為nil
func (e *entry) unexpungeLocked() (wasExpunged bool) {
   return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}

寫操作分為以下幾種情況:

  • 數(shù)據(jù)在read中
  • 數(shù)據(jù)在read中但已經(jīng)被標(biāo)記為刪除
  • 數(shù)據(jù)在dirty中
  • 一個(gè)全新的數(shù)據(jù)

當(dāng)數(shù)據(jù)在read中時(shí),Store會嘗試通過原子操作修改數(shù)據(jù),如果原子操作成功,則相當(dāng)于數(shù)據(jù)更新完成;

具體的代碼如下:

func (e *entry) tryStore(i *any) bool {
   for {
       // 1.獲取entry具體的值
      p := atomic.LoadPointer(&e.p)
      // 2.如果數(shù)據(jù)已經(jīng)被標(biāo)記刪除,則返回false
      if p == expunged {
         return false
      }
      // 3.更新當(dāng)前值,并返回true
      if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
         return true
      }
   }
}

當(dāng)數(shù)據(jù)在read中已經(jīng)被標(biāo)記為刪除,此時(shí)需要重新將entry加入到dirty中,并更新值(這里本質(zhì)上增加了一個(gè)新的entry只是服用了之前entry的地址空間)

當(dāng)數(shù)據(jù)在dirty中時(shí),則直接通過原子操作更新entry的指針;

當(dāng)數(shù)據(jù)是一個(gè)新數(shù)據(jù)時(shí),會創(chuàng)建一個(gè)新的entry加入到dirty中,并且如果是dirty中的第一個(gè)數(shù)據(jù)則會執(zhí)行dirtyLocked方法,將read中當(dāng)前的數(shù)據(jù)(未標(biāo)記刪除的)加入到dirty中。

dirtyLocked的具體實(shí)現(xiàn)如下:

func (m *Map) dirtyLocked() {
    //1 dirty之前是nil的情況才可以進(jìn)行重置操作
   if m.dirty != nil {
      return
   }
   // 2 獲取read中的數(shù)據(jù)
   read, _ := m.read.Load().(readOnly)
   // 3 初始化dirty
   m.dirty = make(map[any]*entry, len(read.m))
   // 4 遍歷read
   for k, e := range read.m {
      // 4.1 將非nil且未被標(biāo)記為刪除的對象加入到dirty中
      if !e.tryExpungeLocked() {
         m.dirty[k] = e
      }
   }
}
// 判斷entry是否被標(biāo)記為刪除,如果entry的值為nil,則將其標(biāo)記為刪除
func (e *entry) tryExpungeLocked() (isExpunged bool) {
    // 1 獲取entry的值
   p := atomic.LoadPointer(&e.p)
   for p == nil {
       // 2 如果當(dāng)前entry的值為空,則嘗試將此key標(biāo)記為刪除
      if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
         return true
      }
      p = atomic.LoadPointer(&e.p)
   }
   // 3 判斷p是否為被標(biāo)記為刪除
   return p == expunged
}

通過上面的分析我們可以發(fā)現(xiàn)當(dāng)寫操作頻繁時(shí)存在以下幾個(gè)問題

  • dirty中存在大量數(shù)據(jù),而read的查詢會大概率無法命中,從而導(dǎo)致查詢需要查詢r(jià)ead和dirty兩個(gè)map且有額外的冗余操作,所以讀性能被大大降低
  • 頻繁的無法命中導(dǎo)致dirty數(shù)據(jù)的晉升,雖然晉升時(shí)只是進(jìn)行指針切換及dirty的清空,但每次晉升后的第一次寫入都會導(dǎo)致dirty對read進(jìn)行拷貝,大大降低性能。
  • 每次寫操作為了因?yàn)椴淮_定數(shù)據(jù)是在read還是dirty或者新數(shù)據(jù)需要進(jìn)行額外的檢查和操作
  • dirty中和read中在某些情況下存在數(shù)據(jù)重復(fù),內(nèi)存占用會高一些

綜上,在寫操作比較頻繁的時(shí)候,sync.Map的各方面性能都大大降低;而對于一些只有極少寫操作的數(shù)據(jù)(比如:只在服務(wù)器啟動時(shí)加載一次的表格數(shù)據(jù)),sync.Map可以提高并發(fā)操作的性能。

如何刪除數(shù)據(jù)

在上面的dirtyLoacked方法中我們看到當(dāng)初始化dirty后,會遍歷read中的數(shù)據(jù),將非nil且未被標(biāo)記為刪除的對象加入到dirty中。由此可以看出read中的數(shù)據(jù)在刪除時(shí)并不會立刻刪除只是將對象標(biāo)記為nil或者expunged。

具體代碼如下:(Delete方法本質(zhì)上就是執(zhí)行的LoadAndDelete)

func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
   read, _ := m.read.Load().(readOnly)
   // 1 從read中查詢數(shù)據(jù)
   e, ok := read.m[key]
   // 2 如果read中不存在,且amended標(biāo)記顯示dirty中存在read中沒有的key,則去dirty中查詢
   if !ok && read.amended {
      // 3.1開始操作dirty,需要加鎖保證線程安全
      m.mu.Lock()
      read, _ = m.read.Load().(readOnly)
      // 3.2 重新從read中檢查一次,避免在Lock執(zhí)行前dirty中的數(shù)據(jù)觸發(fā)了晉升到read的操作
      e, ok = read.m[key]
      if !ok && read.amended {
         // 3.3 從dirty中查詢
         e, ok = m.dirty[key]
         // 3.4 從dirty中刪除
         delete(m.dirty, key)
         // 3.5 無論是否從dirty中查詢到數(shù)據(jù),都相當(dāng)于從read中miss了,需要更新miss計(jì)數(shù)(滿足條件后會觸發(fā)dirty晉升)
         m.missLocked()
      }
      m.mu.Unlock()
   }
   // 4 和load一樣,這里ok為true可能是在read中讀取到數(shù)據(jù)或者dirty中讀取到數(shù)據(jù),
   // dirty中的話雖然已經(jīng)刪除但需要清空entry中的指針p
   if ok {
      // 5 標(biāo)記刪除
      return e.delete()
   }
   return nil, false
}

如上述代碼第5部分所示,無論entry存在哪里,最終都需要將entry標(biāo)記為刪除。如果存在read中會在dirty初始化時(shí)不被加入到dirty中,當(dāng)dirty再次晉升時(shí)read中的數(shù)據(jù)也就被拋棄了。如果存在dirty中則直接清空了數(shù)據(jù)并標(biāo)記entry被刪除。

sync.Map的Range方法

sync.Map并不支持遍歷,但卻提供了一個(gè)Range方法,此方法并不是和range關(guān)鍵字一樣對map的遍歷。

Range方法的具體作用:

  • 遍歷所有read中的元素,對其中的每個(gè)元素執(zhí)行函數(shù)f
  • 如果當(dāng)任何一個(gè)元素作為參數(shù)執(zhí)行函數(shù)f返回false,則立刻中斷遍歷

雖然在執(zhí)行初始階段Range會將dirty的數(shù)據(jù)晉升一次,但仍然不能保證在執(zhí)行過程中沒有新的數(shù)據(jù),所以Range只是遍歷了最新的read中的數(shù)據(jù),而非全部數(shù)據(jù)。

// 遍歷sync.Map
func (m *Map) Range(f func(key, value any) bool) {
   read, _ := m.read.Load().(readOnly)
   // 1 如果存在未晉升的數(shù)據(jù),則先進(jìn)行一次dirty數(shù)據(jù)晉升
   if read.amended {
      m.mu.Lock()
      read, _ = m.read.Load().(readOnly)
      if read.amended {
         read = readOnly{m: m.dirty}
         m.read.Store(read)
         m.dirty = nil
         m.misses = 0
      }
      m.mu.Unlock()
   }
   // 2 遍歷read中的所有entry,分別執(zhí)行f函數(shù)
   for k, e := range read.m {
      v, ok := e.load()
      if !ok {
         continue
      }
      // 3 當(dāng)某個(gè)entery執(zhí)行f函數(shù)返回false,則中斷遍歷
      if !f(k, v) {
         break
      }
   }
}

其他

問:什么時(shí)候清除被標(biāo)記刪除的value

答:當(dāng)首次向dirty中存入數(shù)據(jù)時(shí),會觸發(fā)dirty復(fù)制read中的內(nèi)容,此時(shí)再復(fù)制時(shí)只復(fù)制了非nil且未被標(biāo)記刪除的entry,當(dāng)dirty再次晉升時(shí)就覆蓋掉了read中的數(shù)據(jù),實(shí)現(xiàn)被標(biāo)記刪除的entry的刪除。

以上就是Golang sync.Map底層實(shí)現(xiàn)場景示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang sync.Map底層實(shí)現(xiàn)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang使用MinIO的方案詳解

    Golang使用MinIO的方案詳解

    這篇文章主要介紹了Golang使用MinIO的過程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • Go語言模型:string的底層數(shù)據(jù)結(jié)構(gòu)與高效操作詳解

    Go語言模型:string的底層數(shù)據(jù)結(jié)構(gòu)與高效操作詳解

    這篇文章主要介紹了Go語言模型:string的底層數(shù)據(jù)結(jié)構(gòu)與高效操作詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 使用golang實(shí)現(xiàn)在屏幕上打印進(jìn)度條的操作

    使用golang實(shí)現(xiàn)在屏幕上打印進(jìn)度條的操作

    這篇文章主要介紹了使用golang實(shí)現(xiàn)在屏幕上打印進(jìn)度條的操作,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • Go語言基礎(chǔ)閉包的原理分析示例詳解

    Go語言基礎(chǔ)閉包的原理分析示例詳解

    這篇文章主要為大家介紹了Go語言基礎(chǔ)閉包的底層原理及實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2021-11-11
  • 深入了解Golang中reflect反射的使用

    深入了解Golang中reflect反射的使用

    這篇文章主要介紹了深入了解Golang中reflect反射的使用,Go語言中的反射是一種機(jī)制,可以在運(yùn)行時(shí)動態(tài)地獲取類型信息和操作對象,以及調(diào)用對象的方法和屬性等,需要詳細(xì)了解可以參考下文
    2023-05-05
  • go語言接口用法實(shí)例分析

    go語言接口用法實(shí)例分析

    這篇文章主要介紹了go語言接口用法,實(shí)例分析了Go語言接口的定義及使用技巧,需要的朋友可以參考下
    2015-03-03
  • Go語言實(shí)現(xiàn)猜數(shù)字小游戲

    Go語言實(shí)現(xiàn)猜數(shù)字小游戲

    這篇文章主要為大家詳細(xì)介紹了Go語言實(shí)現(xiàn)猜數(shù)字小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-10-10
  • go語言中sort包的實(shí)現(xiàn)方法與應(yīng)用詳解

    go語言中sort包的實(shí)現(xiàn)方法與應(yīng)用詳解

    golang中也實(shí)現(xiàn)了排序算法的包sort包,所以下面這篇文章主要給大家介紹了關(guān)于go語言中sort包的實(shí)現(xiàn)方法與應(yīng)用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • Go ORM的封裝解決方式詳解

    Go ORM的封裝解決方式詳解

    這篇文章主要為大家介紹了Go ORM的封裝解決方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • 實(shí)時(shí)通信的服務(wù)器推送機(jī)制 EventSource(SSE) 簡介附go實(shí)現(xiàn)示例代碼

    實(shí)時(shí)通信的服務(wù)器推送機(jī)制 EventSource(SSE) 簡介附go實(shí)現(xiàn)示例代碼

    EventSource是一種非常有用的 API,適用于許多實(shí)時(shí)應(yīng)用場景,它提供了一種簡單而可靠的方式來建立服務(wù)器推送連接,并實(shí)現(xiàn)實(shí)時(shí)更新和通知,這篇文章主要介紹了實(shí)時(shí)通信的服務(wù)器推送機(jī)制 EventSource(SSE)簡介附go實(shí)現(xiàn)示例,需要的朋友可以參考下
    2024-03-03

最新評論