golang内置的map不是并发安全的,在v1.9版本提供了并发安全的map: sync.map
func TestSyncMap(t *testing.T) {
m := sync.Map{}
m.Store("name", "xiaoming")
if item, ok := m.Load("name"); ok {
fmt.Println(item)
}
m.Delete("name")
}
sync.map的使用方式和map类似,数据存储、获取、删除和遍历
如果使用map 加锁进行数据操作时,耗时相对较多,sync.map采用了对数据进行读写分离,读时不加锁,用空间换时间
type Map struct {
// 锁
mu Mutex
// 存储仅读数据
read atomic.Value // readOnly
// 存储写入数据
dirty map[any]*entry
// 计数器
misses int
}
进行read操作时,不需要加锁,数据类型为atomic.Value,保证了数据的获取和将dirty赋值给read时的原子性,数据存储的是readOnly, readOnly内部存储的是值的指针,和dirty值的指针一致
func (m *Map) Store(key, value any) {
// 先从read中查找key
// 如果找到数据则更新数据, read和dirty数据存储的都是指针,修改其中一个值,另外一个同时被修改
// 如果在read中数据被标记为删除,则继续查找
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
// 加锁
m.mu.Lock()
// 双检查
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
// 如果read的数据被标记为删除,则将数据设置为nil
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.
// 老数据存储到dirty
m.dirty[key] = e
}
// 存储最新数据
e.storeLocked(&value)
} else if e, ok := m.dirty[key]; ok {
// read中没有,dirty中有对应的key,更新数据
e.storeLocked(&value)
} else {
// read和dirty都没有时
// amended 表示read和dirty数据是否不一致
// 数据一致时
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.
// 将read未被标记删除的数据,刷新到dirty中
m.dirtyLocked()
//
m.read.Store(readOnly{m: read.m, amended: true})
}
// dirty存储新值
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
func (m *Map) Load(key any) (value any, ok bool) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// 数据不在read中,并且read和dirty数据不一致
if !ok && read.amended {
m.mu.Lock()
// Avoid reporting a spurious miss if m.dirty got promoted while we were
// blocked on m.mu. (If further loads of the same key will not miss, it's
// not worth copying the dirty map for this key.)
// 双检查
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
// 直接从dirty中获取
e, ok = m.dirty[key]
// Regardless of whether the entry was present, record a miss: this key
// will take the slow path until the dirty map is promoted to the read
// map.
// 每次在read中没有查到并且两者数据不一致,进行计数,达到阈值后,将dirty复制给read, dirty设置为nil
m.missLocked()
}
m.mu.Unlock()
}
if !ok {
return nil, false
}
// 返回存在的数据
return e.load()
}
func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// read中没有,并且read和dirty数据不一致
if !ok && read.amended {
m.mu.Lock()
// 加锁后 双检查
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
// 直接删除dirty
e, ok = m.dirty[key]
delete(m.dirty, key)
// Regardless of whether the entry was present, record a miss: this key
// will take the slow path until the dirty map is promoted to the read
// map.
// read中数据不存在并且 read和dirty数据不一致进行计数,达到阈值后,将dirty复制给read,dirty设置为nil
m.missLocked()
}
m.mu.Unlock()
}
if ok {
return e.delete()
}
return nil, false
}