Map嵌套+Mutex,Go高並發操作Map


  眾所周知關於Go的Map引用類型在多協程並發使用的時候不是協程安全的,使用Map進行並發修改時,如果低並發可能恰巧卡時間僥幸躲過。但高並發就沒那么僥幸了:fatal error: concurrent map read and map write

  為什么不使用sync.Map

  因此大部分人可能會尋求使用sync.Map來保證協程安全,讀寫不沖突。先照搬一下sync.Map的一般的使用和適用場景:

  • 無須初始化,直接聲明即可。普通的Map是需要用make初始化的,sync.Map:
    • var scene sync.Map
  • sync.Map 不能使用 map 的方式進行取值和設置等操作,而是使用 sync.Map 的方法進行調用,Store 表示新增和修改,Load 表示獲取,Delete 表示刪除
    • // 存鍵值,也可以做修改操作
      scene.Store("london", 100)
      // 根據鍵取值
      fmt.Println(scene.Load("london"))
      // 根據鍵刪除鍵值對
      scene.Delete("london")
    • 而這三個函數的實現其實是通過鎖機制完成的,因此實現了多線程並發情況下的互斥操作Map  
  • sync.Map其設計的初衷也只是解決大並發讀寫的問題,且非常適合於大量讀少量寫的情況,但是當sync.Map存在大量新增和刪除時,會導致 dirty map 頻繁更新,甚至在 miss 過多導致 dirty 成為 nil,並進入重建。其實sync.Map是由一下兩個Map來實現的:(各Map的具體作用與Load,Store等方法的源碼可參考 此處   
    • type Map struct {
          mu sync.Mutex
          read atomic.Value // readOnly
          dirty map[interface{}]*entry // 也就是說dirty map是由鎖來實現load和store的
          misses int
      }
      
      type readOnly struct {
          m       map[interface{}]*entry
          amended bool // true if the dirty map contains some key not in m.
      }
      
      // entry則是map中的value
      type entry struct {
          p unsafe.Pointer // *interface{}
      }
  • 網上關於sync.Map與Map的性能比較:( 原文鏈接

sync.Map的性能高體現在讀操作遠多於寫操作的時候。 極端情況下,只有讀操作時,是普通map的性能的44.3倍。

反過來,如果是全寫,沒有讀,那么sync.Map還不如加普通map+mutex鎖呢。只有普通map性能的一半。

建議使用sync.Map時一定要考慮讀定比例。當寫操作只占總操作的<=1/10的時候,使用sync.Map性能會明顯高很多。

  Map嵌套+Mutex實現方法

import "sync"

type Map struct {
    m map[string]map[string]string // 自定義K-V的類型
    sync.RWMutex
}

// 使用時初始化一個Map
var theMap = &Map{ m: make(map[string]map[string]string) }

// 讀出相應Key的子Map
func (m *Map) LoadChildMap(key string) (value map[string]string, ok bool) {
    m.RLock()
    defer m.RUnlock()
    value, ok = m.m[key]
    return
}

// 讀出相應Key的子Map里相應childKey的val
func (m *Map) LoadChildMapVal(key string, childKey string) (value string, ok bool) {
    m.RLock()
    defer m.RUnlock()
    valueMap, isOk := m.m[key]
    if !isOk {
        value, ok = "", false
        return
    }
    value, ok = valueMap[childKey]
    return
}

// 增加或修改相應Key的子Map
func (m *Map) StoreChildMap(key string, value map[string]string) {
    m.Lock()
    defer m.Unlock()
    m.m[key] = value
}

// 增加或修改相應Key的子Map里相應childKey的val
func (m *Map) StoreChildMapKV(key string, childKey string, value string) {
    m.Lock()
    defer m.Unlock()
    childMap, ok := m.m[key]
    if !ok {
        var newMap = make(map[string]string)
        newMap[childKey] = value
        m.m[key] = newMap
        return
    }
    childMap[childKey] = value
}

// 刪除相應Key的子Map
func (m *Map) DeleteChildMap(key string) {
    m.Lock()
    defer m.Unlock()
    delete(m.m, key)
}

// 刪除相應Key的子Map里的相應childKey的val
func (m *Map) DeleteChildMapVal(key string, childKey string) {
    m.Lock()
    defer m.Unlock()
    childMap, ok := m.m[key]
    if !ok {
        return
    }
    delete(childMap, childKey)
}
  • 需要注意的是,以上所有方法都是從頭到尾加鎖,若覺得粒度太大可以自己定義(甚至可以Map+Mutex整個結構嵌套,給每個子Map都設一把單獨的Mutex鎖,實現更小的粒度)。
  • 在自行定義方法的時候,若僅僅在   value, ok = m.m[key]  的前后加鎖解鎖從而達到減小粒度,但請注意不要在解鎖后對value的任何值執行修改操作,因為Map的嵌套,value的類型是Map類型,是引用類型,對value進行操作實際上是對其指針進行操作,等效於繞過了鎖機制直接進行修改,極易發生 fatal error: concurrent map writes
    • 解決辦法
      • 將子Map值拷貝(需要深拷貝),待改好之后若需要更新到Map中,就再請求一個鎖
      • 仍然遵守鎖機制,待所有修改完后才解鎖
    • 當如果Map的value不是Map,Slice這類引用類型時,而是值傳遞的int,string等等,就無需考慮


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM