众所周知关于Go的Map引用类型在多协程并发使用的时候不是协程安全的,使用Map进行并发修改时,如果低并发可能恰巧卡时间侥幸躲过。但高并发就没那么侥幸了:fatal error: concurrent map read and map write

  为什么不使用sync.Map

  因此大部分人可能会寻求使用sync.Map来保证协程安全,读写不冲突。先照搬一下sync.Map的一般的使用和适用场景:

var scene sync.Map
// 存键值,也可以做修改操作
scene.Store("london", 100)
// 根据键取值
fmt.Println(scene.Load("london"))
// 根据键删除键值对
scene.Delete("london")
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的性能的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等等,就无需考虑