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

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是存在并发读写的问题&#xff0c;在并发读写时候会抛出异常 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 参数说明&#xff1a; -DgroupId 组织Id&#xff08;项目包名&#xff09; -DartifactId 项目名称或模块…...

2023年度总结—你是你的年度MVP吗?

这段年度总结其实我之前就想写了&#xff0c;大概就是市赛比完之后18号的样子把&#xff0c;但是因为太懒了就一直拖到了现在哈哈&#xff0c;我思来想去&#xff0c;翻来覆去&#xff0c;彻夜难眠&#xff0c;想了想&#xff0c;还是决定把它写了吧&#xff01;毕竟&#xff0…...

Linux基础知识学习3

vim编辑器 其分为四种模式 1.普通(命令)模式 2.编辑模式 3.底栏模式 4.可视化模式 vim编辑器被称为编辑器之神&#xff0c;而Emacs更是神之编辑器 普通模式&#xff1a; 1.光标移动 ^ 移动到行首 w 跳到下一个单词的开头…...

Leetcode5-在长度2N的数组中找出重复N次的元素(961)

1、题目 给你一个整数数组 nums &#xff0c;该数组具有以下属性&#xff1a; nums.length 2 * n. nums 包含 n 1 个 不同的 元素 nums 中恰有一个元素重复 n 次 找出并返回重复了 n 次的那个元素。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3,3] 输出&#xff1a…...

openssl的 openssl.cnf配置文件详解

背景&#xff1a;在上一篇文中&#xff0c;提到要写一篇openssl 配置文件详解的&#xff0c;这就来了~~~ find / -name openssl.cnf /etc/pki/tls/openssl.cnf /etc/pki/tls/openssl.cnf&#xff0c;该文件主要设置了证书请求、签名、crl相关的配置。主要相关的伪命令为ca和req…...

SpringBoot集成支付宝,看这一篇就够了。

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

数据结构程序设计——哈希表的应用(2)->哈希表解决冲突的方法

目录 实验须知 代码实现 实验报告 一&#xff1a;问题分析 二、数据结构 1.逻辑结构 2.物理结构 三、算法 &#xff08;一&#xff09;主要算法描述 1.用除留余数法构造哈希函数 2.线性探测再散列法 &#xff08;一&#xff09;主要算法实现代码 四、上机调试 实…...

微信小程序开发系列-07组件

微信小程序开发系列目录 《微信小程序开发系列-01创建一个最小的小程序项目》《微信小程序开发系列-02注册小程序》《微信小程序开发系列-03全局配置中的“window”和“tabBar”》《微信小程序开发系列-04获取用户图像和昵称》《微信小程序开发系列-05登录小程序》《微信小程序…...

JavaScript 中 Set 和 Map 的区别

JavaScript 中的 Set 和 Map 都是用来存储数据的数据结构&#xff0c;它们之间的区别如下&#xff1a; Set 是一组唯一值的集合&#xff0c;而 Map 是一组键值对的集合。Set 中的值是唯一的&#xff0c;不允许重复&#xff1b;Map 中的键是唯一的&#xff0c;值可以重复。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…...

浅谈冯诺依曼体系和操作系统

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

Good Bye 2023

Good Bye 2023 Good Bye 2023 A. 2023 题意&#xff1a;序列a中所有数的乘积应为2023&#xff0c;现在给出序列中的n个数&#xff0c;找到剩下的k个数并输出&#xff0c;报告不可能。 思路&#xff1a;把所有已知的数字乘起来&#xff0c;判断是否整除2023&#xff0c;不够…...

多开工具对手机应用响应速度的优化与改进

多开工具对手机应用响应速度的优化与改进 摘要&#xff1a; 如今&#xff0c;手机应用的多样化和个性化需求不断增长&#xff0c;用户对应用的响应速度要求也越来越高。为了满足用户的需求&#xff0c;开发者们使用了多种技术手段进行应用的优化和改进。其中&#xff0c;多开工…...

文件批量整理,文件归类整理,文件批量归类

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

Python+Django+Mysql+SimpleUI搭建后端用户管理系统(非常详细,每一步都清晰,列举了里面所有使用的方法属性)

一、在Anaconda环境下创建虚拟环境 &#xff08;1&#xff09;打开Anaconda Prompt(install)&#xff0c;创建虚拟环境&#xff0c;如下图所示&#xff1a; 方法一&#xff1a;默认情况下虚拟环境创建在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&#xff5c;代理&#xff08;上&#xff09;&#xff1a;ReAct框架&#xff0c;推理与行动的协同 在之前介绍的思维链&#xff08;CoT&#xff09;中&#xff0c;我向你展示了 LLMs 执行推理轨迹的能力。在给出答案之前&#xff0c;大模型通过中间推理步骤&#xff08;尤其…...

毫秒格式化

## 计算当前毫秒数&#xff1a; const [start,setStart] useState(new Date().getTime())useEffect(()>{setInterval(()>{setCurrMill(new Date().getTime()-start)},1)},[]) ## 格式化毫秒 function formatMilliseconds(milliseconds) {const totalSeconds Math.flo…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

Axure零基础跟我学:展开与收回

亲爱的小伙伴,如有帮助请订阅专栏!跟着老师每课一练,系统学习Axure交互设计课程! Axure产品经理精品视频课https://edu.csdn.net/course/detail/40420 课程主题:Axure菜单展开与收回 课程视频:...

RKNN开发环境搭建2-RKNN Model Zoo 环境搭建

目录 1.简介2.环境搭建2.1 启动 docker 环境2.2 安装依赖工具2.3 下载 RKNN Model Zoo2.4 RKNN模型转化2.5编译C++1.简介 RKNN Model Zoo基于 RKNPU SDK 工具链开发, 提供了目前主流算法的部署例程. 例程包含导出RKNN模型, 使用 Python API, CAPI 推理 RKNN 模型的流程.   本…...