sync.Map源码解析
sync.Map源码解析
Hbzhtdtype Map struct { |
type readOnly struct { |
type entry struct { |
- read map和dirty都为空. 新增数据写入dirty map
- misses次数 >= len(dirtry) ,提升dirty, 将dirty拷贝到 read
增
- 直接操作dirty
删除
read中存在该元素, 直接置为nil
a. amended = false (dirty 中不存在该元素)
b. amended = true ( dirty 中存在该元素) 此时dirty和read中同时为nil
read中不存在该元素, 该元素刚刚写入dirty且未升级成read,
直接调用delete删除dirty中该元素
优化点
空间换时间。通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。
使用只读数据(read),避免读写冲突。
动态调整,miss次数多了之后,将dirty数据提升为read。
double-checking(双重检测)。
延迟删除。 删除一个键值只是打标记,只有在提升dirty的时候才清理删除的数据。
优先从read读取、更新、删除,因为对read的读取不需要锁。
源码分析
- Load 读取
func (m *Map) Load(key interface{}) (value interface{}, ok bool) { |
- missLocked
func (m *Map) missLocked() { |
- load: 绑定在 *entry 上的方法
func (e *entry) load() (value interface{}, ok bool) { |
store 增加和修改
func (m *Map) Store(key, value interface{}) {
// 如果read存在这个键,并且这个entry没有被标记删除,尝试直接写入,写入成功,则结束
// 第一次检测
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) { // 如果read中存在key, 判断p是否为expunged, 不为expunged 更新value 直接返回
return
}
// 此时会有两种情况 1. read中没有key,在dirty中写 2. read有key, 但p = expunged, 需要更新dirty
// 所以都要对dirty进行操作
m.mu.Lock() // 上锁
// 第二次检测
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok { // read 存在 key
// 如果元素被标识为expunged 修改read的expunged为nil
if e.unexpungeLocked() {
m.dirty[key] = e // 值的指针相同
}
// 更新read map 元素值
e.storeLocked(&value) // read和dirty同时修改,此时的e为read的指针
} else if e, ok := m.dirty[key]; ok {
// 此时read map没有该元素,但是dirty map有该元素,并需修改dirty map元素值为最新值
// 添加操作
e.storeLocked(&value) // 直接操作dirty,此时的e为dirty的指针
} else { // key两者都不在, 是一个新的key
if !read.amended { // amended = false, 表明dirty 中的key在 read中都存在
//此时就是判断dirty是否为空, 因为当发生了dirty提升时,dirty的所有key都在read中
//提升完后, dirty会被置为nil, 所以这里amended = false代表dirty为空
m.dirtyLocked() // 根据read重置dirty, 保证dirty包含map中所有key
// 设置read.amended==true,说明dirty map有数据
m.read.Store(readOnly{m: read.m, amended: true})
// 创建了一个新的readOnly对象, 保留原有数据并且amended为true
// amended为true 因为接下来dirty中要插入新值,且这个值不存在于read
}
// 设置元素进入dirty map,此时dirty map拥有read map和最新设置的元素
m.dirty[key] = newEntry(value)
}
// 解锁,有人认为锁的范围有点大,假设read map数据很大,那么执行m.dirtyLocked()会耗费花时间较多,完全可以在操作dirty map时才加锁,这样的想法是不对的,因为m.dirtyLocked()中有写入操作
m.mu.Unlock()
}
tryStore
func (e *entry) tryStore(i *interface{}) bool {
}
// 通过使用 for 循环,可以在竞态条件发生时重新尝试写入操作,直到成功为止。
for {
p := atomic.LoadPointer(&e.p) // 取出p
// expunged: dirty不为空, 且dirty不存在该key,此时不能直接修改read,还要更新dirty
if p == expunged {
return false
} ;
// 原子操作更新value(read 和 dirty 都被更新)
if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
return true
}
// 此时p为 nil || 正常值
// 当p为nil时:
// a. dirty为空(return false)
// b. dirty存在该key, 修改read会同时修改dirty(正常值同等)
// 原子操作更新value:
// 1. 获取 e.p 的当前值。
// 2. 将获取到的值与 p 进行比较。
// 3. 如果两个指针的值相等,则将 e.p 的值替换为 unsafe.Pointer(i)。
// 4. 返回替换操作的结果,表示替换是否成功。
}
}unexpungeLocked
func (e *entry) unexpungeLocked() (wasExpunged bool) {
// 1. 检查 e.p 的当前值是否与 expunged 相等。
// 2. 如果相等,将 e.p 的值替换为 nil,并返回 true 表示替换成功。
// 3. 如果不相等,不进行替换,直接返回 false 表示替换失败。
// 通过这个原子操作,可以确保在并发情况下,只有一个 goroutine 成功地将 e.p 的值从 expunged 替 换为 nil。其他 goroutine 将获得替换失败的结果。
return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}storeLocked
func (e * endtry) storeLocked(i *interface{}) {
atomic.StorePointer(&e.p, unsafe.Pointer(i))
// 1. 获取 e.p 的地址,即 &e.p。
// 2. 将指针 unsafe.Pointer(i) 存储到 &e.p 所指向的地址中。
}dirtyLocked
func (m *Map) dirtyLocked() {
if m.dirty != nil { // 如果dirty对象已经存在,不需要再创建,直接返回
return
}
//根据read重新创建一个对象
read, _ := m.read.Load().(readOnly)
m.dirty = make(map[interface{}]*entry, len(read.m)) // 新建一个dirty对象
// 遍历 read中的key, value, 复制到dirty中
for k, e := range read.m {
// 如果标记为nil或者expunged,则不复制到dirty map
if !e.tryExpungeLocked() {
m.dirty[k] = e
}
}
}tryExpungedLocked
func (e *entry) tryExpungedLocked() (isExpunged bool) {
p := atomic.LoadPointer(&e.p)
for p == nil {
//将nil的值改为expunged, 这样做是为了表明在对dirty重构前,这个key只存在read,不存在dirty
if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
return true
}
p = atomic.LoadPointer(&e.p)
}
return p == expunged
}
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果






