日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

sync.Map低层工作原理详解

發布時間:2024/4/11 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 sync.Map低层工作原理详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

sync.Map低層工作原理詳解


目錄

  • 為什么需要sync.Map?適合什么場景?
  • sync.Map內部實現基本原理及結構體分析
  • sync.Map低層工作原理

  • 1. 為什么需要sync.Map?適合什么場景?

  • map在并發讀寫的情況下是不安全,會觸發panic。Go 1.9 版本中提供了一種效率較高的并發安全的 sync.Map,目的是為了改善多核高讀取低寫入時候的性能,即適合寫特別少而并發讀特別多的場景。

  • 2. sync.Map內部實現基本原理及結構體分析

  • sync.Map通過內部存儲的兩個map來實現了優化:分別是鍵(key) 固定的read表和包含所有鍵值對的dirty。所有對read上已有的鍵值對的增刪改查操作都是無鎖實現,考慮到的是“寫特別少幾乎固定”的場景,因為基本用不上鎖,從而大大提高了性能。
  • 無論是read還是dirty,存儲的都是值的地址,而且是共享地址的。也就是說所有對read的無鎖增刪改查都會同步反饋在dirty上。所以對read增刪改查沒有經過dirty而dirty卻始終反映最新值。
  • 1. sync.Map結構體

  • sync.Map結構體
  • type Map struct {mu Mutex // 互斥鎖,用以處理并發讀寫的問題read atomic.Value // 包含可以并發訪問的map部分,可以直接Load,但Store必須先持有mudirty map[interface{}]*entry //包含需要持有mu才能訪問的部分,為確保dirty快速升級為read,dirty中還包含read中未刪除的部分misses int //記錄從dirty表查詢的次數。misses大到超過dirty拷貝的消耗時,會直接將dirty提升至read,后續的store操作會生成新的dirty。 }
  • read的類型為readOnly,其結構如下:
  • type readOnly struct {m map[interface{}]*entryamended bool // true if the dirty map contains some key not in m. dirty表有沒有新數據的標記 amended }type entry struct {p unsafe.Pointer // *interface{} }
  • readOnly包含一個map m及一個dirty表有沒有新數據的標記 amended(false表示dirty沒有新數據),最終的數據存儲在entry中,entry存儲的是數據的指針。

  • 3. sync.Map低層工作原理

    1. Store處理過程

    // Store sets the value for a key. func (m *Map) Store(key, value interface{}) {read, _ := m.read.Load().(readOnly) // 獲取read表if e, ok := read.m[key]; ok && e.tryStore(&value) { // 如果read表中能找到對應key,嘗試插入read表對應的key中return}m.mu.Lock()read, _ = m.read.Load().(readOnly) // 對m加鎖,重新獲取read表,避免虛假報告if e, ok := read.m[key]; ok { // 在read表能查詢到,但標記為expunged,則dirty表現創建entry,再存入數據到dirty表if e.unexpungeLocked() {// The entry was previously expunged, which implies that there is a// non-nil dirty map and this entry is not in it.m.dirty[key] = e}e.storeLocked(&value)} else if e, ok := m.dirty[key]; ok { // read表不存在對應key,則判斷dirty表是否存在,存在則進行store操作e.storeLocked(&value)} else {if !read.amended { // 如果// We're adding the first new key to the dirty map.// Make sure it is allocated and mark the read-only map as incomplete.m.dirtyLocked()m.read.Store(readOnly{m: read.m, amended: true})}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}} }
  • 比如:key為“hello”,value為“world”,則read和dirty分別存儲的內容如下圖所示
  • 調用Store(“hello”,“not world”)。如果read中有對應的鍵,那么不會上鎖訪問dirty,而是直接無鎖替換,變成下圖所示。dirty始終反映著最新值。
  • 一直訪問read上的鍵值對,由于不用加鎖,性能可以大大提高。但是read表是不會更新的,如果后面新增了鍵值對寫入dirty,然后頻繁訪問新的鍵值對,這時就需要加鎖訪問dirty表。
  • 如果Load方法調用需要加鎖的次數達到一定次數,表示read表的數據太少了,那么sync.Map會將dirty表設置為read表,dirty表為nil。
    用上圖的例子,比方說一直頻繁調用Load(key4),那么sync.Map就會更換read表
  • 當要求新增read表中沒有的鍵值對的時候,sync.Map會重建新的dirty表。需要復制read表中的數據,復制的是地址,所以dirty表和read表對同一個鍵仍然共享地址。
  • 由于在沒有新增鍵值對以前,所有的增刪改查都在read表中實現。假如說在新增鍵值對之前我們刪除掉了key5,從而使其地址為nil,當發現read表中有指向nil的,則將其修改為一個特殊標記expunged,然后跳過該鍵值,如下圖所示。
  • 如果key5再也沒有出現,這樣不僅dirty表節省了空間,而且隨著read表轉換成dirty表,read表也少了消耗。但是之后出現了Store(key5,“another value”),sync.Map會檢查是否有著"expunged"標記,如果有的話,會加鎖然后讓dirty表先創建對應的鍵值對(如下圖所示),然后更新值

  • 2. Load獲取過程

  • load獲取過程,見注釋
  • func (m *Map) Load(key interface{}) (value interface{}, ok bool) {read, _ := m.read.Load().(readOnly)e, ok := read.m[key]if !ok && read.amended { // 如果read表沒有找到則判斷amended為true,表示dirty有比read表多新元素m.mu.Lock()// Avoid reporting a spurious miss if m.dirty got promoted while we were// blocked on m.mu. (If further loads of the same key will not miss, it's// not worth copying the dirty map for this key.) // 鎖住m,再去read表查詢是否有想要的數據,再查一遍是為了防止dirty轉為read表,避免虛假報告read, _ = m.read.Load().(readOnly)e, ok = read.m[key]if !ok && read.amended { // 再查詢一次還是沒有查詢到,那么從dirty表中查找e, ok = m.dirty[key]// Regardless of whether the entry was present, record a miss: this key// will take the slow path until the dirty map is promoted to the read// map.m.missLocked() // 判斷messes次數是否小于len(dirty),大于時,將dirty表轉成read表,dirty表置為nil}m.mu.Unlock()}if !ok { // 如果read表和dirty都沒有查詢到,返回nilreturn nil, false}return e.load() // 找到了,返回數據 }func (m *Map) missLocked() {m.misses++if m.misses < len(m.dirty) { // 如果misses小于len(dirty),繼續從dirty查詢,大于時將dirty轉為read表且dirty置為nilreturn}m.read.Store(readOnly{m: m.dirty}) // 將dirty轉為read表且dirtym.dirty = nil // dirty表置為nilm.misses = 0 }func (e *entry) load() (value interface{}, ok bool) {p := atomic.LoadPointer(&e.p)if p == nil || p == expunged {return nil, false}return *(*interface{})(p), true }

    總結

    以上是生活随笔為你收集整理的sync.Map低层工作原理详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。