当前位置: 首页 > news >正文

源码角度分析 sync.map

源码版本:go 1.21

适用条件

参考:https://medium.com/@okoanton/the-internals-of-sync-map-and-its-performance-comparison-with-map-rwmutex-e000e148600c
Map类型针对两种常见用例进行了优化:(1)给定键的条目只写一次,但读了很多次,就像在只增长的缓存中一样(读操作较多且键相对稳定)
(2)多个例程读取、写入和覆盖不相交的键集的条目。在这两种情况下,与Go Map与单独的Mutex或RWMutex配对相比,使用Map可以显著减少锁争用。

究竟是使用读写锁还是sync.map其实是要看你的使用场景是否会不停的触发sync.map的加锁,下面通过源码分析什么时候会加锁?

源码分析

这里的图是搬运自水印这里的博主
同时分析经典的读写函数,其他的api大家可以根据这个思路自行分析,最终找到适合自己场景的实现方式
读流程
写流程


// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.package toolimport ("fmt""sync""sync/atomic"
)type Map struct {mu     sync.Mutexread   atomic.Pointer[readOnly] //原子化的只读mapdirty  map[any]*entry  // 这里的entry其实和read里的是一个指向,指针决定了不会出现数据不一致问题misses int // 统计read miss的次数
}type readOnly struct {m       map[any]*entryamended bool // dirty中是否有readonly种没有的数据
}// 硬删除态
var expunged = new(any)type entry struct {p atomic.Pointer[any]
}func newEntry(i any) *entry {e := &entry{}e.p.Store(&i)return e
}func (m *Map) loadReadOnly() readOnly {if p := m.read.Load(); p != nil {return *p}return readOnly{}
}func (m *Map) Load(key any) (value any, ok bool) {read := m.loadReadOnly()e, ok := read.m[key]// 不存在且数据有diffif !ok && read.amended {// double check的目的是:防止在获取锁的过程中readonly被整体更新了m.mu.Lock()read = m.loadReadOnly()e, ok = read.m[key]if !ok && read.amended {e, ok = m.dirty[key]/*当miss次数超过dirty的长度时,会触发dirty->read的拷贝amended置为falseamended在写数据时才可能被置为true*/m.missLocked()}m.mu.Unlock()}if !ok {return nil, false}return e.load()
}func (e *entry) load() (value any, ok bool) {p := e.p.Load()if p == nil || p == expunged {return nil, false}return *p, true
}// Store sets the value for a key.
func (m *Map) Store(key, value any) {_, _ = m.Swap(key, value)
}// Swap swaps the value for a key and returns the previous value if any.
// The loaded result reports whether the key was present.
func (m *Map) Swap(key, value any) (previous any, loaded bool) {read := m.loadReadOnly()if e, ok := read.m[key]; ok {// 硬删除态就去加锁了,否则尝试CAS替换if v, ok := e.trySwap(&value); ok {if v == nil {return nil, false}return *v, true}}/*为什么又是double check?因为再次期间可能别的读操作导致了dirty->read的覆盖*/m.mu.Lock()read = m.loadReadOnly()if e, ok := read.m[key]; ok {// 硬删除态的恢复if e.unexpungeLocked() {m.dirty[key] = e}// 写入新值到read中if v := e.swapLocked(&value); v != nil {loaded = trueprevious = *v}} else if e, ok := m.dirty[key]; ok {if v := e.swapLocked(&value); v != nil {loaded = trueprevious = *v}} else {// 全新数据的插入if !read.amended {fmt.Println("dirtyLocked")// 数据拷贝以及软硬删除态数据的过滤m.dirtyLocked()m.read.Store(&readOnly{m: read.m, amended: true})}m.dirty[key] = newEntry(value)}m.mu.Unlock()return previous, loaded
}func (e *entry) unexpungeLocked() (wasExpunged bool) {return e.p.CompareAndSwap(expunged, nil)
}// swapLocked unconditionally swaps a value into the entry.
//
// The entry must be known not to be expunged.
func (e *entry) swapLocked(i *any) *any {return e.p.Swap(i)
}func (m *Map) missLocked() {m.misses++if m.misses < len(m.dirty) {return}fmt.Println("misses:", m.misses)m.read.Store(&readOnly{m: m.dirty})m.dirty = nilm.misses = 0
}func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {read := m.loadReadOnly()e, ok := read.m[key]if !ok && read.amended {m.mu.Lock()read = m.loadReadOnly()e, ok = read.m[key]if !ok && read.amended {e, ok = m.dirty[key]delete(m.dirty, key)m.missLocked()}m.mu.Unlock()}if ok {return e.delete()}return nil, false
}// Delete deletes the value for a key.
func (m *Map) Delete(key any) {m.LoadAndDelete(key)
}func (e *entry) delete() (value any, ok bool) {for {p := e.p.Load()if p == nil || p == expunged {return nil, false}if e.p.CompareAndSwap(p, nil) {return *p, true}}
}// trySwap swaps a value if the entry has not been expunged.
//
// If the entry is expunged, trySwap returns false and leaves the entry
// unchanged.
func (e *entry) trySwap(i *any) (*any, bool) {for {p := e.p.Load()if p == expunged {return nil, false}if e.p.CompareAndSwap(p, i) {return p, true}}
}func (m *Map) Range(f func(key, value any) bool) {read := m.loadReadOnly()if read.amended {m.mu.Lock()read = m.loadReadOnly()if read.amended {read = readOnly{m: m.dirty}m.read.Store(&read)m.dirty = nilm.misses = 0}m.mu.Unlock()}for k, e := range read.m {v, ok := e.load()if !ok {continue}if !f(k, v) {break}}
}func (m *Map) dirtyLocked() {if m.dirty != nil {return}read := m.loadReadOnly()m.dirty = make(map[any]*entry, len(read.m))for k, e := range read.m {if !e.tryExpungeLocked() {m.dirty[k] = e}}
}func (e *entry) tryExpungeLocked() (isExpunged bool) {p := e.p.Load()for p == nil {if e.p.CompareAndSwap(nil, expunged) {return true}p = e.p.Load()}return p == expunged
}

一些问题

  1. dirty和read中的数据如何进行拷贝?
    a. miss次数超过dirty的长度后,触发dirty->read的拷贝;并且把amended置为false,表示此时read中已经是全部数据了
    b. 当插入写操作发生并且amended为false时,会触发read->dirty的拷贝(此时会把软删除改为硬删除,然后彻底消失)
  2. 二者的数据会不会不一致,同一个key不同value?
    a. 不会,因为二者共用一个指针
  3. 什么情况会加锁呢?
    a. 读数据miss,并且amended为true,此时一定会加锁 double check;
    b. 写数据时:read中不存在(或者已经是硬删除态了)
    c. 如果有协程分别在进行读miss和写miss的操作,就会不定的触发加锁,并且dirty<->read的来回拷贝,并且随着数据越来越多,每次进行read->dirty的拷贝时都会线性的遍历所有数据
  4. 什么情况下read和dirty都有数据? 首先写穿透,此时read = nil, ditry有数据; 然后读穿透覆盖回来,此时再写穿透再覆盖回去。之后再进行写的话,就是dirty > read了

相关文章:

  • C++智能指针滥用带来的性能与内存问题有哪些
  • 阿里云bgp服务器优势有哪些?搭建bgp服务器怎么做?
  • Mangodb基本概念和介绍,Mango三个重要的概念:数据库,集合,文档
  • Python在自动驾驶仿真环境中的应用:构建智能驾驶的虚拟世界
  • MySQL基础关键_001_认识
  • 判断图片url损坏无法展示工具类
  • WEB漏洞--CSRF及SSRF案例
  • UNIAPP项目记录
  • Transformer-LSTM-SVM回归
  • 学习基本咖啡知识
  • C# 中重启程序通常意味着关闭当前运行的应用程序实例
  • 豪越科技消防立库方案:实现应急物资高效管理
  • 基于STM32、HAL库的DS28E15P安全验证及加密芯片驱动程序设计
  • 纯C协程框架NtyCo
  • 智慧交警系统架构设计方案
  • RHCE第七章:SElinux
  • leetcode继续c++10/100
  • NOC科普一
  • 明远智睿SSD2351开发板:开启嵌入式开发新篇程
  • CertiK创始人顾荣辉出席Unchained Summit,探讨Web3.0安全与合规路径
  • 海量数据处于“原矿”状态,数据价值释放如何破局?
  • 普京与卢卡申科举行会晤,将扩大在飞机制造等领域合作
  • 何立峰出席驻沪中央金融机构支持上海建设国际金融中心座谈会并讲话
  • 中办、国办印发《安全生产考核巡查办法》
  • 成都警方:在地铁公共区域用改装设备偷拍女乘客,男子被行拘
  • 书业观察|一本书的颜值革命:从毛边皮面到爆火的刷边书