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

如何避免go的map競態(tài)問題的方法

 更新時間:2023年02月09日 09:21:03   作者:有想法的工程師  
本文主要介紹了如何避免go的map競態(tài)問題的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

背景

在使用go語言開發(fā)的過程中,我碰到過這樣一種情況,就是代碼自測沒問題,代碼檢查沒問題,上線跑了一段時間時間了也沒問題,就是突然偶爾會抽風panic,導致程序所在的pod(k8s的運行docker鏡像的最小單位)重啟了,而程序里拋出來的異常如下

,意思是多個協程正在同時對同一個map變量進行讀寫,這個就涉及到go程序的競態(tài)問題,而競態(tài)問題也是我們日常開發(fā)中遇到比較多的情況

為什么會出現競態(tài)問題

出現這個問題的主要原因是有多個協程在對同一個map變量進行修改,這樣就可能會出現map被一個協程修改到一半的時候,然后另外一個協程就來讀取了,導致讀到一個“半成品”的map變量。而這個就說明一個問題,就是map類型并不是并發(fā)安全的

而并發(fā)安全的定義就是:在高并發(fā)下,進程、線程(協程)出現資源競爭,導致出現臟讀,臟寫,死鎖等情況。

那么go語言有如下幾種類型不具備并發(fā)安全:map,slice,struct,channel,string

不過奇怪的是,只有map類型發(fā)生并發(fā)競爭的時候,才會拋出fatal error,這個是無法被recover的,一定會中斷程序,而這也導致程序運行的pod會被檢測出異常從而重啟

查了資料,有一種說法是,map大部分會被用來存配置文件,而配置文件出錯可能會導致一些嚴重的業(yè)務問題,所以寧愿程序崩潰也要保全業(yè)務數據不會出現臟數據(只是一種說法,不用太過在意)

如何解決競態(tài)問題

1、使用go的一些并發(fā)原語

如果需要修改的變量是程序啟動之后就不需要修改的配置,那么可以使用sync.Once包來處理,這個包的作用就是限制一件事情只做一次,示例代碼如下

type User struct {
   Name string
   Other map[string]interface{}
   ConfigOnce sync.Once
}
 
// InitConfigOnce
// @description "初始化配置信息,只執(zhí)行一次"
// @auth yezibin 2023-01-21 15:38:09
// @param name string "description"
// @param other map[string]interface{} "description"
// @return *User "description"
func (u *User)InitConfigOnce(name string, other map[string]interface{}) *User {
   //Do包起來的方法,只會執(zhí)行一次,但是必須是同一個sync.Once變量
   u.ConfigOnce.Do(func() {
      fmt.Println("ok")
      u.Name = name
      u.Other = other
   })
   return u
}
 
// GetUserConfig
// @description "打印配置文件"
// @auth yezibin 2023-01-21 15:38:36
func (u *User) GetUserConfig()  {
   fmt.Println(u)
}

2、加讀寫鎖(RWMutex map)

出現競態(tài)的本質是因為多個協程對同一個變量同時進行讀與寫,通過用鎖來防止這個情況,因為我舉得案例是讀多寫少的情況,用上讀寫鎖性能會更好,示例代碼如下

type Mmap struct {
   Data map[string]interface{}
   Mu sync.RWMutex //因為主要是配置,屬于讀多寫少情況,所以使用讀寫鎖提高鎖的性能
}
 
 
// InitMmap
// @description "初始化讀寫鎖的map結構體"
// @auth yezibin 2023-01-21 00:09:30
// @return *Config "description"
func InitMmap() *Mmap {
   return &Mmap{
      Data: make(map[string]interface{}),
   }
}
// Get
// @description "獲取配置map數據"
// @auth yezibin 2023-01-21 00:10:09
// @param name string "description"
// @return interface{} "description"
func (m *Mmap) Get(name string) interface{} {
   m.Mu.RLock()
   defer m.Mu.RUnlock()
   return m.Data[name]
}
 
// Set
// @description "批量設置map的值"
// @auth yezibin 2023-02-05 13:08:17
// @param data map[string]interface{} "description"
func (m *Mmap) Set(data map[string]interface{})  {
   m.Mu.Lock()
   defer m.Mu.Unlock()
   for k, v := range data {
      m.Data[k] = v
   }
 
}
// SetOne
// @description "設置配置map數據"
// @auth yezibin 2023-01-21 00:10:23
// @param key string "description"
// @param val string "description"
func (m *Mmap) SetOne(key, val string)  {
   m.Mu.Lock()
   defer m.Mu.Unlock()
   m.Data[key] = val
}

建議

1、如果屬于讀多寫少的情況,盡量選擇讀寫鎖來減少鎖住的范圍,從而提高讀寫性能

2、這里推薦將需要用來讀寫的map變量和鎖共同組建一個struct,這樣能保證讀和寫上的是同一把讀寫鎖,同時也方便整合對map變量的操作

3、分片加鎖

方案2中雖然加了讀寫鎖,比加一把普通的鎖要性能高些,不過鎖的粒度還是大了些,當高并發(fā)來襲時,寫的操作必然會阻塞讀的動作,那么有沒有辦法將鎖住的范圍縮小一些呢

思路:如果給map里的每個元素加鎖,每次修改只是單個元素的鎖生效,其他沒改到的元素就正常讀,這樣鎖的粒度會更細,這就是分片加鎖的原理

這種就是將一把“大”鎖拆成一把把小鎖,是一種空間換時間的方法

實現上,已經有人實現了好用的具有分片鎖的map,庫地址:https://github.com/orcaman/concurrent-map

import (
   cmap "github.com/orcaman/concurrent-map"
   "sync"
)
// InitCmap
// @description "初始化分片鎖的map"
// @auth yezibin 2023-02-05 14:08:17
// @return *cmapConfig "description"
func InitCmap() *cmapConfig {
   return &cmapConfig{
      cmap.New(),
   }
}
 
// Set
// @description "批量往map寫入元素"
// @auth yezibin 2023-02-05 14:10:02
// @param config map[string]interface{} "description"
func (c *cmapConfig) Set(config map[string]interface{})  {
   for k, v := range config{
      c.Cmap.Set(k, v)
   }
}
 
// Get
// @description "從map獲取元素"
// @auth yezibin 2023-02-05 14:10:22
// @param k string "description"
// @return interface{} "description"
func (c *cmapConfig) Get(k string) interface{} {
   v, ok := c.Cmap.Get(k)
   if ok {
      return v
   } else {
      return nil
   }
}

4、go的原生可并發(fā)map

最后還會跟大家介紹一個go原生庫里就有一個可并發(fā)讀寫的map,這個放在sync庫

官方的文檔中指出,在以下兩個場景中使用 sync.Map,會比使用 map+RWMutex 的方式,性能要好得多:

1、只會增長的緩存系統中,一個 key 只寫入一次而被讀很多次;

2、多個 goroutine 為不相交的鍵集讀、寫和重寫鍵值對。

原理:sync.Map結構里有兩個字段,一個read,一個dirty。dirty包含read的所有字段,新增字段是寫在dirty上,有個miss變量用戶訪問到read沒有,但是dirty有的數據次數

  • 空間換時間。通過冗余的兩個數據結構(只讀的 read 字段、可寫的 dirty),來減少加鎖對性能的影響。對只讀字段(read)的操作不需要加鎖。優(yōu)先從 read 字段讀取、更新、刪除,因為對 read 字段的讀取不需要鎖。
  • 動態(tài)調整。miss 次數多了之后,將 dirty 數據提升為 read,避免總是從 dirty 中加鎖讀取。double-checking。加鎖之后先還要再檢查 read 字段,確定真的不存在才操作 dirty 字段。
  • 延遲刪除。刪除一個鍵值只是打標記,只有在提升 dirty 字段為 read 字段的時候才清理刪除的數據。

示例代碼

type syncMapConfig struct {
   Smap sync.Map
}
// InitSmap
// @description "初始化sync.map"
// @auth yezibin 2023-02-05 15:43:08
// @return *syncMapConfig "description"
func InitSmap() *syncMapConfig {
   return &syncMapConfig{
      sync.Map{},
   }
}
// Set
// @description "批量寫入map"
// @auth yezibin 2023-02-05 15:43:57
// @param config map[string]interface{} "description"
func (s *syncMapConfig) Set(config map[string]interface{})  {
   for k, v := range config {
      s.Smap.Store(k, v)
   }
}
// Get
// @description "從map里獲取數據"
// @auth yezibin 2023-02-05 15:44:09
// @param k string "description"
// @return interface{} "description"
func (s *syncMapConfig) Get(k string) interface{} {
   c, ok := s.Smap.Load(k)
   if ok {
      return c
   } else {
      return nil
   }
}

性能對比

上面說了4種方法,處理用once這個包比較特殊(map只寫一次,以后只讀),其他都是可讀寫多次的,有可比性,那么2,3,4這三種方案的性能對比如何呢,哪種情況下該用哪種呢

標注:下面數據對比,帶有相關字符的有如下含義

字符含義字符含義
Cmap使用了concurrent-map包WnR寫和讀一樣多次
Smap使用了sync.Map包WnRMore讀多寫少
Mmap使用RWMutexWMorenR寫多讀少

當并發(fā)=1000,對map是部分更新,且不是更新讀取的字段 

當讀寫一樣多的時候性能: sync.Map > concurrent-map > RWMutex map

當讀多寫少的時候性能:concurrent-map > sync.Map >  RWMutex map 

當寫多讀少的時候性能:sync.Map > concurrent-map > RWMutex map 

結論:當高并發(fā)對map進行讀寫時,如果寫的字段和讀的字段錯開的時候

concurrent-map 在讀多寫少的情況下有優(yōu)勢,因為鎖的粒度小

sync.Map  在寫多讀少的情況下有優(yōu)勢,因為有結構設計有優(yōu)勢

而讀寫鎖因為加鎖粒度大,導致高并發(fā)下性能都不是很好

當并發(fā)=1000,對map是更新和讀取都是同一個字段

當讀寫一樣多的時候性能: sync.Map > RWMutex map > concurrent-map

當讀多寫少的時候性能:sync.Map > RWMutex map > concurrent-map 

當寫多讀少的時候性能:sync.Map > concurrent-map > RWMutex map 

在讀寫都是同一個map字段的時候,sync.Map的結構優(yōu)勢就凸顯了,因為對讀和寫是針對sync.Map 結構里的read字段,且不加鎖;而其他兩個包都是會上鎖的

當并發(fā)=10,對map是部分更新,且不是更新讀取的字段

當讀寫一樣多的時候性能: RWMutex map > sync.Map > concurrent-map

當讀多寫少的時候性能:RWMutex map > sync.Map > concurrent-map 

當寫多讀少的時候性能:RWMutex map > concurrent-map  > sync.Map

當并發(fā)變低的情況下,RWMutex map的性能就好于其他兩種,主要原因是并發(fā)低,鎖的競爭和阻塞情況變少,反而是結構簡單不需要占用大空間的RWMutex map形式要更好

當并發(fā)=10,對map是更新和讀取都是同一個字段

當讀寫一樣多的時候性能: RWMutex map > sync.Map > concurrent-map

當讀多寫少的時候性能:RWMutex map > sync.Map > concurrent-map 

當寫多讀少的時候性能:RWMutex map  > sync.Map > concurrent-map

當并發(fā)變低的情況下,RWMutex map的性能就好于其他兩種,主要原因是并發(fā)低,鎖的競爭和阻塞情況變少,反而是結構簡單不需要占用大空間的RWMutex map形式要更好

最終結論

選用哪個方式,其實主要先看并發(fā)數,其次看讀寫模式,再來選擇使用哪種模式,以下表格是選用最優(yōu)解

讀多寫少寫多讀少
并發(fā)高concurrent-mapsync.Map
并發(fā)低RWMutex mapRWMutex map

到此這篇關于如何避免go的map競態(tài)問題的方法的文章就介紹到這了,更多相關go map競態(tài)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關文章

  • golang限流庫兩個大bug(半年之久無人提起)

    golang限流庫兩個大bug(半年之久無人提起)

    最近我的同事在使用uber-go/ratelimit[1]這個限流庫的時候,遇到了兩個大?bug,這兩個?bug?都是在這個庫的最新版本(v0.3.0)中存在的,而這個版本從?7?月初發(fā)布都已經過半年了,都沒人提?bug,難道大家都沒遇到過么
    2023-12-12
  • 解決Go gorm踩過的坑

    解決Go gorm踩過的坑

    這篇文章主要介紹了解決Go gorm踩過的坑,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • go實現redigo的簡單操作

    go實現redigo的簡單操作

    golang操作redis主要有兩個庫,go-redis和redigo,今天我們就一起來介紹一下redigo的實現方法,需要的朋友可以參考下
    2018-07-07
  • 快速掌握Go 語言 HTTP 標準庫的實現方法

    快速掌握Go 語言 HTTP 標準庫的實現方法

    基于HTTP構建的服務標準模型包括兩個端,客戶端(Client)和服務端(Server),這篇文章主要介紹了Go 語言HTTP標準庫的實現方法,需要的朋友可以參考下
    2022-07-07
  • GoLand如何設置中文

    GoLand如何設置中文

    這篇文章主要介紹了GoLand如何設置中文,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • Go語言題解LeetCode561數組拆分

    Go語言題解LeetCode561數組拆分

    這篇文章主要為大家介紹了Go語言題解LeetCode561數組拆分示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Golang實現組合模式和裝飾模式實例詳解

    Golang實現組合模式和裝飾模式實例詳解

    這篇文章主要介紹了Golang實現組合模式和裝飾模式,本文介紹組合模式和裝飾模式,golang實現兩種模式有共同之處,但在具體應用場景有差異。通過對比兩個模式,可以加深理解,需要的朋友可以參考下
    2022-11-11
  • Golang設計模式之組合模式講解

    Golang設計模式之組合模式講解

    這篇文章主要介紹了Golang設計模式之組合模式,組合模式針對于特定場景,如文件管理、組織管理等,使用該模式能簡化管理,使代碼變得非常簡潔
    2023-01-01
  • Go一站式配置管理工具Viper的使用教程

    Go一站式配置管理工具Viper的使用教程

    Viper是一個方便Go語言應用程序處理配置信息的庫,它可以處理多種格式的配置,這篇文章主要為大家介紹了它的具體使用教程,需要的可以參考下
    2023-08-08
  • Golang單元測試與斷言編寫流程詳解

    Golang單元測試與斷言編寫流程詳解

    這篇文章主要介紹了Golang單元測試與斷言編寫流程,單元測試也是一個很重要的事情。單元測試是指在開發(fā)中,對一個函數或模塊的測試。其強調的是對單元進行測試
    2022-12-12

最新評論