Go语言syncMap实现的功能和使用场景
在go语言提供的标准map类型中,如果涉及到协程读取map的场景,会存在并发读写的问题,并发操作map的会导致数据不一致,
为什么map不是并发安全的?
因为map的读写操作不是原子操作,当多个协程同时读写map时,会发生脏读和脏写导致数据不一致的问题。
在go 语言1.9版本后引入了syncMap,可以提供更加便捷的并发map操作方法, 例如:
var m sync.Map
可以使用syncMap的方法来进行并发安全的map操作,例如:
m.Store(key, value)
m.Load(key)
m.Delete(key)
这些方法都是并发安全的,不会导致数据不一致的问题。
但是该数据类型仅适合一定程度上的读多写少场景,syncMap内部使用了读写锁来实现并发安全,当写操作比较多时,会导致读操作的阻塞,影响性能。 源码:https://github.com/golang/go/blob/go1.23.0/src/sync/map.go
func (m *Map) Swap(key, value any) (previous any, loaded bool) {
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
}
}
m.mu.Lock()
read = m.loadReadOnly()
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {
// The entry was previously expunged, which implies that there is a
// non-nil dirty map and this entry is not in it.
m.dirty[key] = e
}
if v := e.swapLocked(&value); v != nil {
loaded = true
previous = *v
}
} else if e, ok := m.dirty[key]; ok {
if v := e.swapLocked(&value); v != nil {
loaded = true
previous = *v
}
} else {
if !read.amended {
// We're adding the first new key to the dirty map.
// Make sure it is allocated and mark the read-only map as incomplete.
m.dirtyLocked()
m.read.Store(&readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
return previous, loaded
}
总结
- 如果遇到并发写入更多更频繁的场景,需要考虑读写锁频繁阻塞,性能会大幅下降,
- 应该考虑使用其他的方式实现该需求,可以考虑使用redis hash或者将并发操作的场景转换为在异步task进程中进行处理。