亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Go語言如何解決map并發(fā)安全問題

 更新時間:2024年04月11日 11:30:21   作者:shark_chili  
常說go語言是一門并發(fā)友好的語言,對于并發(fā)操作總會在編譯期完成安全檢查,所以這篇文章我們就來聊聊go語言是如何解決map這個數據結構的線程安全問題吧

常說go語言是一門并發(fā)友好的語言,對于并發(fā)操作總會在編譯期完成安全檢查,所以這篇文章我們就來聊聊go語言是如何解決map這個數據結構的線程安全問題。

詳解map中的并發(fā)安全問題

問題復現

我們通過字面量的方式創(chuàng)建一個map集合,然后開啟兩個協程,其中協程1負責寫,協程2負責讀:

func main() {
 //創(chuàng)建map
 m := make(map[int]string)
 //聲明一個長度為2的倒計時門閂
 var wg sync.WaitGroup
 wg.Add(2)

 //協程1寫
 go func() {
  for true {
   m[0] = "xiaoming"
  }
  wg.Done()
 }()

 //協程2讀
 go func() {
  for true {
   _ = m[0]
  }
  wg.Done()

 }()

 wg.Wait()
 fmt.Println("結束")
}

在完成編譯后嘗試運行

fatal error: concurrent map read and map write 

并發(fā)操作失敗的原因

我們直接假設一個場景,協程并發(fā)場景下當前的map處于擴容狀態(tài),假設我們的協程1修改了key-111對應的元素觸發(fā)漸進式驅逐操作,使得key-111移動到新桶上,結果協程2緊隨其后嘗試讀取key-111對應的元素,結果得到nil,由此引發(fā)了協程安全問題:

上鎖解決并發(fā)安全問題

Java一樣,go語言也有自己的鎖sync.Mutex,我們在協程進行map操作前后進行上鎖和釋放的鎖的操作,確保單位時間內只有一個協程在操作map,從而實現協程安全,因為這種鎖是排他鎖,這使得協程的并發(fā)特性得不到發(fā)揮:

var mu sync.Mutex


func main() {
 //創(chuàng)建map
 m := make(map[int]string)
 
 var wg sync.WaitGroup
 wg.Add(2)

 //協程1上鎖后寫
 go func() {

  for true {
   mu.Lock()
   m[0] = "xiaoming"
   mu.Unlock()
  }
  wg.Done()
 }()

 //協程2上鎖后讀
 go func() {
  for true {
   mu.Lock()
   _ = m[0]
   mu.Unlock()
  }
  wg.Done()

 }()

 wg.Wait()
 fmt.Println("結束")
}

使用自帶的sync.map進行并發(fā)讀寫

好在go語言為我們提供的現成的"輪子",即sync.Map,我們直接通過其內置函數storeload即可實現并發(fā)讀寫還能保證協程安全:

func main() {
 //創(chuàng)建sync.Map
 var m sync.Map
 
 
 var wg sync.WaitGroup
 wg.Add(2)

 //協程1并發(fā)寫
 go func() {

  for true {
   m.Store(1, "xiaoming")
  }
  wg.Done()
 }()

 //協程2并發(fā)讀
 go func() {
  for true {
   m.Load(1)
  }
  wg.Done()

 }()

 wg.Wait()
 fmt.Println("結束")
}

詳解sync.map并發(fā)操作流程

常規(guī)sync.map并發(fā)讀或寫

sync.map會有一個readdirty指針,指向不同的key數組,但是這些key對應的value指針都是一樣的,這意味著這個map不同桶的相同key共享同一套value

進行并發(fā)讀取或者寫的時候,首先拿到一個原子類型的read指針,通過CAS嘗試修改元素值,如果成功則直接返回,就如下圖所示,我們的協程通過CAS完成原子指針數值讀取之后,直接操作read指針所指向的map元素,通過key定位到value完成修改后直接返回。

sync.map修改或追加

接下來再說說另一種情況,假設我們追加一個元素key-24,通過read指針進行讀取發(fā)現找不到,這就意味當前元素不存在或者在dirty指針指向的map下,所以我們會先上重量級鎖,然后再上一次read鎖。 分別到readdirty指針上查詢對應key,進行如下三部曲:

  • 如果在read發(fā)現則修改。
  • 如果在dirty下發(fā)現則修改。
  • 都沒發(fā)現則說明要追加了,則將amended設置為true說明當前map臟了,嘗試將元素追加到dirty指針管理的map下。

這里需要補充一句,通過amended可知當前map是否處于臟寫狀態(tài),如果這個標志為true,后續(xù)每次讀寫未命中都會對misses進行自增操作,一旦未命中數達到dirty數組的長度(大抵是想表達所有未命中的都在dirty數組上)閾值就會進行一次dirty提升,將dirty的key提升為read指針指向的數組,確保提升后續(xù)并發(fā)讀寫的命中率:

sync.map并發(fā)刪除

并發(fā)刪除也和上述并發(fā)讀寫差不多,都是先通過read指針嘗試是否成功,若不成功則鎖主mutex到dirty進行刪除,所以這里就不多贅述了。

sync.map源碼解析

sync.map內存結構

通過上文我們了解了sync.map的基本操作,這里我們再回過頭看看sync.map的數據結構,即重量級鎖mu Mutex,

type Map struct {
 //重量級鎖
 mu Mutex
 //read指針,指向一個不可變的key數組
 read atomic.Pointer[readOnly]

 //dirty 指針指向可以進行追加操作的key數組
 dirty map[any]*entry

 //當前map讀寫未命中次數
 misses int
}

sync.Map并發(fā)寫源碼

并發(fā)寫底層本質是調用Swap進行追加或者修改:

func (m *Map) Store(key, value any) {
 _, _ = m.Swap(key, value)
}

步入swap底層即可看到上文圖解的操作,這里我們給出核心源碼,讀者可自行參閱:

func (m *Map) Swap(key, value any) (previous any, loaded bool) {
 //上read嘗試修改
 read := m.loadReadOnly()
 if e, ok := read.m[key]; ok {
  if v, ok := e.trySwap(&value); ok {
   if v == nil {
    return nil, false
   }
   return *v, true
  }
 }
 //上重量級鎖和read原子指針加載進行修改
 m.mu.Lock()
 read = m.loadReadOnly()
 if e, ok := read.m[key]; ok {
  if e.unexpungeLocked() {
   
   m.dirty[key] = e
  }
  if v := e.swapLocked(&value); v != nil {
   loaded = true
   previous = *v
  }
 } else if e, ok := m.dirty[key]; ok { //如果在dirty數組發(fā)現則上swap鎖進行修改
  if v := e.swapLocked(&value); v != nil {
   loaded = true
   previous = *v
  }
 } else {//上述情況都不符合則將amended 標記為true后進行追加
  if !read.amended {
   
   m.dirtyLocked()
   m.read.Store(&readOnly{m: read.m, amended: true})
  }
  m.dirty[key] = newEntry(value)
 }
 //解鎖返回
 m.mu.Unlock()
 return previous, loaded
}

sync.Map讀取

對應的讀取源碼即加載read原子變量后嘗試到read指針下讀取,若讀取不到則增加未命中數到dirty指針下讀取:

func (m *Map) Load(key any) (value any, ok bool) {
 //加載讀原子變量
 read := m.loadReadOnly()
 //嘗試在read指針下讀取
 e, ok := read.m[key]
 //沒讀取到上mutex鎖到dirty下讀取,若發(fā)現則更新未命中數后返回結果
 if !ok && read.amended {
  m.mu.Lock()
  
  read = m.loadReadOnly()
  e, ok = read.m[key]
  if !ok && read.amended {
   e, ok = m.dirty[key]
   //更新未命中數
   m.missLocked()
  }
  m.mu.Unlock()
 }
 if !ok {
  return nil, false
 }
 return e.load()
}

sync.Map刪除

刪除步驟也和前面幾種操作差不多,這里就不多贅述了,讀者可參考筆者核心注釋了解流程:

func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
 //上讀鎖定位元素
 read := m.loadReadOnly()
 
 e, ok := read.m[key]
 //為命中則上重量級鎖到read和dirty下再次查找,找到了則刪除,若是在dirty下找到還需要額外更新一下未命中數
 if !ok && read.amended {
  m.mu.Lock()
  read = m.loadReadOnly()
  e, ok = read.m[key]
  if !ok && read.amended {
   e, ok = m.dirty[key]
   delete(m.dirty, key)
   //自增一次未命中數
   m.missLocked()
  }
  m.mu.Unlock()
 }
 if ok {
  return e.delete()
 }
 return nil, false
}

// Delete deletes the value for a key.
func (m *Map) Delete(key any) {
 m.LoadAndDelete(key)
}

以上就是詳解Go語言如何解決map并發(fā)安全問題的詳細內容,更多關于Go解決map并發(fā)安全的資料請關注腳本之家其它相關文章!

相關文章

  • Gin框架中的PostForm用法及說明

    Gin框架中的PostForm用法及說明

    這篇文章主要介紹了Gin框架中的PostForm用法及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • 一文帶你掌握Golang的反射基礎

    一文帶你掌握Golang的反射基礎

    go的反射是由其標準庫中的reflect包實現,該reflect包實現了在運行時進行反射的能力,本篇主要介紹了reflect的常用的幾個方法,希望對大家有所幫助
    2023-02-02
  • Go語言切片常考的面試真題解析

    Go語言切片??嫉拿嬖囌骖}解析

    了解最新的Go語言面試題型,讓面試不再是難事,下面這篇文章主要給大家介紹了關于Go語言切片面試??嫉囊恍﹩栴},文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-02-02
  • Golang 處理浮點數遇到的精度問題(使用decimal)

    Golang 處理浮點數遇到的精度問題(使用decimal)

    本文主要介紹了Golang 處理浮點數遇到的精度問題,不使用decimal會出大問題,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • golang中map增刪改查的示例代碼

    golang中map增刪改查的示例代碼

    在Go語言中,map是一種內置的數據結構,用于存儲鍵值對,本文主要介紹了golang中map增刪改查的示例代碼,具有一定的參考價值,感興趣的可以了解一下
    2023-11-11
  • Go語言題解LeetCode268丟失的數字示例詳解

    Go語言題解LeetCode268丟失的數字示例詳解

    這篇文章主要為大家介紹了Go語言題解LeetCode268丟失的數字示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • 詳解Go如何實現協程并發(fā)執(zhí)行

    詳解Go如何實現協程并發(fā)執(zhí)行

    線程是通過本地隊列,全局隊列或者偷其它線程的方式來獲取協程的,目前看來,線程運行完一個協程后再從隊列中獲取下一個協程執(zhí)行,還只是順序執(zhí)行協程的,而多個線程一起這么運行也能達到并發(fā)的效果,接下來就給給大家詳細介紹一下Go如何實現協程并發(fā)執(zhí)行
    2023-08-08
  • golang獲取網卡信息操作

    golang獲取網卡信息操作

    這篇文章主要介紹了golang獲取網卡信息操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go高級特性探究之HTTP錯誤處理詳解

    Go高級特性探究之HTTP錯誤處理詳解

    在Web應用程序中,HTTP錯誤處理是非常重要的,它關系到Web應用程序的穩(wěn)定性和可靠性,本文介紹如何在Go項目中處理HTTP錯誤,并提供相應的解決方案和實踐經驗,希望對Go語言Web應用程序的開發(fā)者有所幫助
    2023-06-06
  • Go語言截取字符串函數用法

    Go語言截取字符串函數用法

    這篇文章主要介紹了Go語言截取字符串函數用法,實例分析了Go語言操作字符串的技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02

最新評論