golang并发安全-sync.map
sync.map解决的问题
golang 原生map是存在并发读写的问题,在并发读写时候会抛出异常
func main() {mT := make(map[int]int)g1 := []int{1, 2, 3, 4, 5, 6}g2 := []int{4, 5, 6, 7, 8, 9}go func() {for i := range g1 {mT[i] = i}}()go func() {for i := range g2 {mT[i] = i}}()time.Sleep(3 * time.Second)
}
抛出异常 fatal error: concurrent map writes
如果将map换成sync.map 那么就不会出现这个问题,下面就简单说说syn.map怎么实现的
基本结构
Map结构体
// Map类型针对两种常见的用例进行了优化:1-当给定键的条目只写一次但读多次时,如在只增长的缓存中,2-当多个goroutine读取、写入和覆盖不相交的键集的条目时。在这两种情况下,与单独的Mutex或RWMutex配对的Go映射相比,使用Map可以显著减少锁争用。
type Map struct { // 互斥锁mu,操作dirty需先获取mu mu Mutex // read是只读的数据结构,可安全并发访问部分,访问它无须加锁,sync.map的所有操作都优先读read // read中存储结构体readOnly,readOnly中存着真实数据,储存数据时候需要加锁// read中可能会存在脏数据:即entry被标记为已删除read atomic.Value // readOnly// dirty是可以同时读写的数据结构,访问它要加锁,新添加的key都会先放到dirty中 // dirty == nil的情况:// 1.被初始化 // 2.提升为read后,但它不能一直为nil,否则read和dirty会数据不一致。 // 当有新key来时,会用read中的数据(不是read中的全部数据,而是未被标记为已删除的数据,)填充dirty // dirty != nil时它存着sync.map的全部数据(包括read中未被标记为已删除的数据和新来的数据)dirty map[interface{}]*entry // 统计访问read没有未命中然后穿透访问dirty的次数 // 若miss等于dirty的长度,dirty会提升成read,提升后可以增加read的命中率,减少加锁访问dirty的次数 misses int
}
readOnly结构体
//第一点的结构read存的就是readOnly
type readOnly struct {m map[any]*entry //m是一个map,key是interface,value是指针entry,其指向真实数据的地址,amended bool // amended等于true代表dirty中有readOnly.m中不存在的entry。
}
entry结构体
type entry struct { // p:// expunged: 删除; nil: 逻辑删除但存在dirty; 数据 p unsafe.Pointer // *interface{}
}
Load方法
代码解说
Load:读取数据
// Load 返回 map 中key 对应的值,如果没有值,则返回 nil。
// ok 结果表示是否在 map 中找到了 value。
func (m *Map) Load(key any) (value any, ok bool) {read, _ := m.read.Load().(readOnly) // 从read 读取数据,并转换readonlye, ok := read.m[key]if !ok && read.amended { // readonly没有找到对应数据m.mu.Lock()// 双重检测:// 再检查一次readonly,以防中间有Map.dirty被替换为readonlyread, _ = m.read.Load().(readOnly)e, ok = read.m[key]if !ok && read.amended { // 去 dirty查找对应数据e, ok = m.dirty[key]// 无论Map.dirty中是否有这个key,miss都加一,// 若miss大小等于dirty的长度,dirty中的元素会被加到Map.read中 m.missLocked()}m.mu.Unlock()}if !ok {return nil, false}return e.load()// 若entry.p被删除(等于nil或expunged)返回nil和不存在(false),否则返回对应的值和存在(true)
}
missLocked:dirty是如何提升为read
func (m *Map) missLocked() {m.misses++ // 每次misses+1if m.misses < len(m.dirty) {return}// 当misses等于dirty的长度,m.dirty转换readOnly,amended被默认赋值成false m.read.Store(readOnly{m: m.dirty})m.dirty = nilm.misses = 0
}
流程图
load: 会先从readOnly查找数据, 如果没有开启加锁,再次访问readOnly, 再次没有再去dirty去查。
Store方法
代码解说
store: 赋值
// Store 设置key value
func (m *Map) Store(key, value any) {read, _ := m.read.Load().(readOnly) // 转换readOnly// 若key在readOnly.m中且 e.tryStore 不为 false(没有逻辑删除)if e, ok := read.m[key]; ok && e.tryStore(&value) {return}m.mu.Lock()// 双重检测:// 再检查一次readonly,以防中间有Map.dirty被替换为readonlyread, _ = m.read.Load().(readOnly)if e, ok := read.m[key]; ok {// entry.p状态是expunged置为nil// 如果是逻辑删除就需要清除标记了if e.unexpungeLocked() {// 之前dirty中没有此key,所以往dirty中添加此key m.dirty[key] = e}// cas: 赋值e.storeLocked(&value)} else if e, ok := m.dirty[key]; ok {e.storeLocked(&value)} else {// dirty中没有新数据,往dirty中添加第一个新key if !read.amended {// 把readOnly中未标记为删除的数据拷贝到dirty中 m.dirtyLocked()// amended:true,现在dirty有readOnly中没有的key m.read.Store(readOnly{m: read.m, amended: true})}m.dirty[key] = newEntry(value)}m.mu.Unlock()
}
tryStore:尝试写入数据
func (e *entry) tryStore(i *any) bool {for { p := atomic.LoadPointer(&e.p) if p == expunged { // 如果逻辑删除就返回false return false } // 不是就将value写入if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) { return true } }
}
dirtyLocked: 将readOnly 未删除的放到dirty
func (m *Map) dirtyLocked() { if m.dirty != nil { return } // dirty为nil时,把readOnly中没被标记成删除的entry添加到dirty read, _ := m.read.Load().(readOnly) m.dirty = make(map[interface{}]*entry, len(read.m)) for k, e := range read.m { // tryExpungeLocked函数在entry未被删除时返回false,反之返回true if !e.tryExpungeLocked() { // entry没被删除 m.dirty[k] = e } }
}
流程图
sync.map不适合用于频繁插入新key-value的场景,因为此操作会频繁加锁访问dirty会导致性能下降。更新操作在key存在于readOnly中且值没有被标记为删除(expunged)的场景下会用无锁操作CAS进行性能优化,否则也会加锁访问dirty。
Delete方法
代码解说
LoadAndDelete:查找删除
func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {read, _ := m.read.Load().(readOnly) e, ok := read.m[key]if !ok && read.amended { // readOnly不存在此key,但dirty中可能存在 // 加锁访问dirty m.mu.Lock() // 双重检测 read, _ = m.read.Load().(readOnly) e, ok = read.m[key] // readOnly不存在此key,但是dirty中可能存在 if !ok && read.amended { e, ok = m.dirty[key] delete(m.dirty, key) m.missLocked() // 判断dirty是否可以转换readOnly,可以就转换} m.mu.Unlock() } if ok { // 如果entry.p不为nil或者expunged,则把逻辑删除(标记为nil) return e.delete() } return nil, false
}
delete:逻辑删除
func (e *entry) delete() (value any, ok bool) {for {p := atomic.LoadPointer(&e.p)if p == nil || p == expunged { // 已经处理或者不存在return nil, false}if atomic.CompareAndSwapPointer(&e.p, p, nil) { // 逻辑删除return *(*any)(p), true}}
}
流程图
Range方法
代码解说
Range:轮训元素
func (m *Map) Range(f func(key, value any) bool) {read, _ := m.read.Load().(readOnly) if read.amended { // 如果dirty存在数据m.mu.Lock() // 双重检测 read, _ = m.read.Load().(readOnly) if read.amended { // readOnly.amended被默认赋值成false read = readOnly{m: m.dirty} m.read.Store(read) m.dirty = nil m.misses = 0 } m.mu.Unlock() } // 遍历readOnly.m for k, e := range read.m { v, ok := e.load() if !ok { continue } if !f(k, v) { break } }
}
流程图
Range:全部key都存在于readOnly中时,是无锁遍历的,性能最优。如果readOnly只存在Map中的部分key时,会一次性加锁拷贝dirty的元素到readOnly,减少多次加锁访问dirty中的数据。
总结
1- sync.map 结构体加了readOnly 和 dirty 来实现读写分离,load,store, delete,range 每次都会优先访问read,后面访问dirty都会双重检测以防加锁前Map.dirty可能已被提升为read
2- sync.map不适合写多读少,从store 代码中可以看出会频繁加锁访问dirty,双重检测等等,这些都会导致性能下降
3- sync.map 没有提供对read, dirty 的长度方法,这个对象使用在于并发场景下,会额外带来锁竞争的问题
4- misses 是 统计访问read没有未命中然后穿透访问dirty的次数 ,如果等于dirty会转换readOnly
5- entry 有三种类型 expunged: 删除; nil: 逻辑删除但存在dirty; 数据 。其中expunged 会在 unexpungeLocked 方法中进行赋值(在store时候会加锁访问dirty,把readOnly中的未被标记为删除的所有entry指针放到dirty,之前被delete方法标记为删除状态的entry=nil都变为expunged,那这些被标记为expunged的entry将不会出现在dirty中。)
相关文章:

golang并发安全-sync.map
sync.map解决的问题 golang 原生map是存在并发读写的问题,在并发读写时候会抛出异常 func main() {mT : make(map[int]int)g1 : []int{1, 2, 3, 4, 5, 6}g2 : []int{4, 5, 6, 7, 8, 9}go func() {for i : range g1 {mT[i] i}}()go func() {for i : range g2 {mT[…...
开发第一个SpringBoot程序
使用命令创建Maven工程 mvn archetype:generate -DgroupIdorg.sang -DartifactIdchapter01 -DarchetypeArtifactIdmaven-archetype-quickstart -DinteractiveModefalse 参数说明: -DgroupId 组织Id(项目包名) -DartifactId 项目名称或模块…...

2023年度总结—你是你的年度MVP吗?
这段年度总结其实我之前就想写了,大概就是市赛比完之后18号的样子把,但是因为太懒了就一直拖到了现在哈哈,我思来想去,翻来覆去,彻夜难眠,想了想,还是决定把它写了吧!毕竟࿰…...

Linux基础知识学习3
vim编辑器 其分为四种模式 1.普通(命令)模式 2.编辑模式 3.底栏模式 4.可视化模式 vim编辑器被称为编辑器之神,而Emacs更是神之编辑器 普通模式: 1.光标移动 ^ 移动到行首 w 跳到下一个单词的开头…...
Leetcode5-在长度2N的数组中找出重复N次的元素(961)
1、题目 给你一个整数数组 nums ,该数组具有以下属性: nums.length 2 * n. nums 包含 n 1 个 不同的 元素 nums 中恰有一个元素重复 n 次 找出并返回重复了 n 次的那个元素。 示例 1: 输入:nums [1,2,3,3] 输出:…...
openssl的 openssl.cnf配置文件详解
背景:在上一篇文中,提到要写一篇openssl 配置文件详解的,这就来了~~~ find / -name openssl.cnf /etc/pki/tls/openssl.cnf /etc/pki/tls/openssl.cnf,该文件主要设置了证书请求、签名、crl相关的配置。主要相关的伪命令为ca和req…...

SpringBoot集成支付宝,看这一篇就够了。
前 言 在开始集成支付宝支付之前,我们需要准备一个支付宝商家账户,如果是个人开发者,可以通过注册公司或者让有公司资质的单位进行授权,后续在集成相关API的时候需要提供这些信息。 下面我以电脑网页端在线支付为例,介…...

数据结构程序设计——哈希表的应用(2)->哈希表解决冲突的方法
目录 实验须知 代码实现 实验报告 一:问题分析 二、数据结构 1.逻辑结构 2.物理结构 三、算法 (一)主要算法描述 1.用除留余数法构造哈希函数 2.线性探测再散列法 (一)主要算法实现代码 四、上机调试 实…...

微信小程序开发系列-07组件
微信小程序开发系列目录 《微信小程序开发系列-01创建一个最小的小程序项目》《微信小程序开发系列-02注册小程序》《微信小程序开发系列-03全局配置中的“window”和“tabBar”》《微信小程序开发系列-04获取用户图像和昵称》《微信小程序开发系列-05登录小程序》《微信小程序…...
JavaScript 中 Set 和 Map 的区别
JavaScript 中的 Set 和 Map 都是用来存储数据的数据结构,它们之间的区别如下: Set 是一组唯一值的集合,而 Map 是一组键值对的集合。Set 中的值是唯一的,不允许重复;Map 中的键是唯一的,值可以重复。Set …...
web前端之JavaScript
MENU JavaScript之设计模式、单例、代理、装饰者、中介者、观察者、发布订阅、策略JavaScript之数组静态方法的实现、reduce、forEach、map、push、every JavaScript之设计模式、单例、代理、装饰者、中介者、观察者、发布订阅、策略 单例模式 概念 保证一个类仅有一个实例&am…...

C# 图标标注小工具-查看重复文件
目录 效果 项目 代码 下载 效果 项目 代码 using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Windows.Forms;namespace ImageDuplicate {public partial clas…...

浅谈冯诺依曼体系和操作系统
🌎冯诺依曼体系结构 文章目录 冯诺依曼体系结构 认识冯诺依曼体系结构 硬件分类 各个硬件的简单认识 输入输出设备 中央处理器 存储器 关于内存 对冯诺依曼体系的理解 操作系统 操作系统…...

Good Bye 2023
Good Bye 2023 Good Bye 2023 A. 2023 题意:序列a中所有数的乘积应为2023,现在给出序列中的n个数,找到剩下的k个数并输出,报告不可能。 思路:把所有已知的数字乘起来,判断是否整除2023,不够…...
多开工具对手机应用响应速度的优化与改进
多开工具对手机应用响应速度的优化与改进 摘要: 如今,手机应用的多样化和个性化需求不断增长,用户对应用的响应速度要求也越来越高。为了满足用户的需求,开发者们使用了多种技术手段进行应用的优化和改进。其中,多开工…...

文件批量整理,文件归类整理,文件批量归类
我们每天都要面对无数的文件,从工作报告、个人照片到电影和音乐。如何有效地管理和归类这些文件,成为了我们日常生活和工作中所要处理的。今天,小编就给大家介绍一款简单易用的工具——文件批量改名高手,助你轻松实现文件批量归类…...

Python+Django+Mysql+SimpleUI搭建后端用户管理系统(非常详细,每一步都清晰,列举了里面所有使用的方法属性)
一、在Anaconda环境下创建虚拟环境 (1)打开Anaconda Prompt(install),创建虚拟环境,如下图所示: 方法一:默认情况下虚拟环境创建在Anaconda安装目录下的envs文件夹中 conda create --name usermanage …...
【Qt-QWidget-QLabel-QFrame-QSlider-View-Bar】
Qt编程指南 ■ Label■ QLabel■ QMovie 显示动画■ Widget■ QWidget■ QTabWidget■ QTableWidget■ QListWidget■ QStackedWidget■ QCalendarWidget■ QFrame■ QFrame■ View■ QT...

11|代理(上):ReAct框架,推理与行动的协同
11|代理(上):ReAct框架,推理与行动的协同 在之前介绍的思维链(CoT)中,我向你展示了 LLMs 执行推理轨迹的能力。在给出答案之前,大模型通过中间推理步骤(尤其…...

毫秒格式化
## 计算当前毫秒数: const [start,setStart] useState(new Date().getTime())useEffect(()>{setInterval(()>{setCurrMill(new Date().getTime()-start)},1)},[]) ## 格式化毫秒 function formatMilliseconds(milliseconds) {const totalSeconds Math.flo…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...