众所周知关于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等等,就无需考虑