当前位置: 首页 > 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了
http://www.xdnf.cn/news/214615.html

相关文章:

  • Silvaco仿真中victory process的蒙特卡洛(Monte Carlo)离子注入
  • [4-06-09].第10节:自动配置- 分析@SpringBootApplication启动类
  • github使用记录
  • Redis分布式锁使用以及对接支付宝,paypal,strip跨境支付
  • 第十六届蓝桥杯大赛网安组--几道简单题的WP
  • HTTP协议重定向及交互
  • 运放参数汇总
  • mac word接入deepseek
  • LVGL -窗口操作
  • Linux/AndroidOS中进程间的通信线程间的同步 - 管道和FIFO
  • 【C++编程入门】:基本语法
  • Java 多线程基础:Thread 类详解
  • 云数据中心整体规划方案PPT(113页)
  • VIT(ICLR2021)
  • foc控制 - clarke变换和park变换
  • 【后端】【Docker】 Docker 动态代理 取消代理完整脚本合集(Ubuntu)
  • 内网服务器映射到公网上怎么做?网络将内网服务转换到公网上
  • 学习基本宠物美容
  • 零基础实现把知识库接到聆思CSK6大模型开发板上
  • 请简述一下什么是 Kotlin?它有哪些特性?
  • C++ 红黑树
  • 第14讲:科研图表的导出与排版艺术——高质量 PDF、TIFF 输出与投稿规范全攻略!
  • Java 基础--运算符全解析
  • Ubuntu搭建 Nginx以及Keepalived 实现 主备
  • ‘WebDriver‘ object has no attribute ‘find_element_by_class‘
  • 咖啡的功效与作用及副作用,咖啡对身体有哪些好处和坏处
  • 什么是缓冲区溢出?NGINX是如何防止缓冲区溢出攻击的?
  • [逆向工程]什么是CPU寄存器(三)
  • Qt开发之C++泛型编程进阶
  • C语言教程(二十五):C 语言函数可变参数详解