map、sync.map、concurrent-map适用场景与源码解析
最近一直加班,无论工作日还是周末,虽然每天很忙但总感觉空空的,很少有时间停下来思考与总结。项目中各种甩锅,最后最苦逼的还是落到了研发的头上,文档编写、环境部署、问题排查虐得一遍又一遍。事情杂乱,研发效率超级低,不知道何是是个头呀
背景
在go中,map是最常用的集合之一。
其底层key存储采用的是hash算法,在数据检索时提供了强大的性能,深受各大开发者喜爱。
但在并发方面,map则存在较为严重的问题。一不留神,就会导致整个程序出错退出。
为了解决map并发操作的问题,诞生出了sync.map及第三方的concurrent-map等集合。
那么哪个集合更符合我们的需求,就需要都来了解一下。
map线程安全问题
先来看一下原生map在并发操作上带来的问题,随便写个测试代码验证一下:
var myMap = make(map[int]int, 0)for i := 0; i < 10; i++ {go func() {for x := 0; x < 1000; x++ {time.Sleep(time.Millisecond)myMap[x] = x + 1fmt.Printf("x=%d m=%d\n", x, myMap[x])}}()}time.Sleep(time.Second * 5)
可能出现如下错误:
x=12 m=13
fatal error: concurrent map writes
x=12 m=13goroutine 27 [running]:
fatal error: concurrent map writes
如上面所示,使用map在并发场景的情况下进行并发读写,程序可能抛出以上错误则会导致程序退出
给map加互斥锁(Mutex)
为了解决以上问题,在代码量改动比较上的情况下则是加上lock锁;
代码改造为:
var myMap = make(map[int]int, 0)var lock sync.Mutexfor i := 0; i < 10; i++ {go func() {lock.Lock()for x := 0; x < 1000; x++ {time.Sleep(time.Millisecond)myMap[x] = x + 1fmt.Printf("x=%d m=%d\n", x, myMap[x])}lock.Unlock()}()}time.Sleep(time.Second * 5)
所有协程都使用同一把lock锁,进行数据读写时先获取锁再执行对map的读写操作。
这种方式对于并发较小的场景一般也能进行处理,对于并发大时则可能会出现耗时过久才能获取锁。
给map加读写锁(RWMutex)
针对上面给map加一把大锁,如果带来了性能不佳的情况,且应用场景为比较明确的读多写少场景的场景,可以进一步优化为读写锁(RWMutex)分离实现,编码上来看也还过得去。
sync.map
下面深入了解一下sync.map是如何解决的golang中map并发安全问题的。
将原代码进行修改:
var myMap = sync.Map{}for i := 0; i < 10; i++ {go func() {for x := 0; x < 1000; x++ {time.Sleep(time.Millisecond)myMap.Store(x, x+1)value, _ := myMap.Load(x)fmt.Printf("x=%d m=%d\n", x, value)}}()}time.Sleep(time.Second * 5)
再看来一下sync.map的源码,代码行数总体不到600行,与go的代码的简短精悍比较符合。
sync.map结构
在了解sync.map的源码前,非常有必要了解一下sync.map的数据存储结构。
type Map struct {mu Mutexread atomic.Pointer[readOnly]dirty map[any]*entrymisses int
}type readOnly struct {m map[any]*entryamended bool // true if the dirty map contains some key not in m.
}type entry struct {p atomic.Pointer[any]
}
如下图:
Store流程
sync.map中每个kv对的新增使用store方法实现。源码如下:
// 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 {if v, ok := e.trySwap(&value); ok {if v == nil {return nil, false}return *v, true}}m.mu.Lock()read = m.loadReadOnly()if e, ok := read.m[key]; ok {if e.unexpungeLocked() {// The entry was previously expunged, which implies that there is a// non-nil dirty map and this entry is not in it.m.dirty[key] = e}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 {// We're adding the first new key to the dirty map.// Make sure it is allocated and mark the read-only map as incomplete.m.dirtyLocked()m.read.Store(&readOnly{m: read.m, amended: true})}m.dirty[key] = newEntry(value)}m.mu.Unlock()return previous, loaded
}
流程图如下:
在store中主要分为2个分支:
- 更新的key在read中存在,使用自旋锁(CAS)的方式对value进行更新
- 将更新的kv对在dirty中进行更新,并确保dirty初始化完毕且amended标识为true
Load流程
sync.map中在kv对被存储后,就可以使用Load方法查询了。
其源码如下:
// Load returns the value stored in the map for a key, or nil if no
// value is present.
// The ok result indicates whether value was found in the map.
func (m *Map) Load(key any) (value any, ok bool) {read := m.loadReadOnly()e, ok := read.m[key]if !ok && read.amended {m.mu.Lock()// Avoid reporting a spurious miss if m.dirty got promoted while we were// blocked on m.mu. (If further loads of the same key will not miss, it's// not worth copying the dirty map for this key.)read = m.loadReadOnly()e, ok = read.m[key]if !ok && read.amended {e, ok = m.dirty[key]// Regardless of whether the entry was present, record a miss: this key// will take the slow path until the dirty map is promoted to the read// map.m.missLocked()}m.mu.Unlock()}if !ok {return nil, false}return e.load()
}
流程图如下:
在了解了Store的流程后再来看Load就比较简单啦。
主要流程为:
- 从readOnly中查询key的值
- readOnly中无法找到,且有新的值被存到map中(amended为true)
- 从dirty中查找
- 将misses自增
- misses大于等于dirty的数量时,将dirty设为readOnly;并重置dirty与misses
需要重点关注的关于dirtry升级为readOnly的代码如下:
func (m *Map) missLocked() {m.misses++if m.misses < len(m.dirty) {return}m.read.Store(&readOnly{m: m.dirty})m.dirty = nilm.misses = 0
}
Delete流程
在看了Store和Load的流程后,对sync.map的主要流程就算是基本掌握了。最后再来看一下Delete的流程:
// Delete deletes the value for a key.
func (m *Map) Delete(key any) {m.LoadAndDelete(key)
}
// LoadAndDelete deletes the value for a key, returning the previous value if any.
// The loaded result reports whether the key was present.
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)// Regardless of whether the entry was present, record a miss: this key// will take the slow path until the dirty map is promoted to the read// map.m.missLocked()}m.mu.Unlock()}if ok {return e.delete()}return nil, false
}
Delete的流程与前面新增和查询流程类似:
- 先从readOnly中查询,存在对应key值调用
e.delete()
进行删除 - readOnly中不存在并被修改过(amended为true),则进行加锁从dirty中查找,并调用delete进行删除
这里需要留意的是,从readOnly中删除kv对也是使用的自旋(CAS)的方式进行删除的,源码如下:
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}}
}
concurrent-map
与JAVA语言类似地,第三方的concurrent-map组件也提供了一种实现用于解决map的并发访问问题。
其项目地址如下:
https://github.com/orcaman/concurrent-map
其实现方式为采用的与JAVA中的ConcurrentHashMap思路实现的,即通过多个锁提高减少对大锁竞争。
与sync.map相比,sync.map中所有的key使用同一个Mutex互斥锁,而在concurrent-map中,则存大多个Mutex互斥锁,多个key共享同一个Mutex互斥锁。
总结
在go中进行KV存储时,常用map、sync.map、concurrent-map这3种map实现。
项目中选型时具体应该使用哪个需要分析具体的业务场景,可参考sync.map中的这段话:
The Map type is optimized for two common use cases: (1) when the entry for a given key is only ever written once but read many times, as in caches that only grow, or (2) when multiple goroutines read, write, and overwrite entries for disjoint sets of keys. In these two cases, use of a Map may significantly reduce lock contention compared to a Go map paired with a separate Mutex or RWMutex.
最后,再简单总结一下:
项目中选型时,应优先考虑使用原生map进行KV存储;
多个协程的并发读写场景,应优先考虑在map中加上互斥锁(Mutex)或读写锁(RWMutex)实现,这样对map的编码方式改动也最小。
并发场景为读多写少的场景,则可考虑sync.map;如并发场景为读多写多的场景,又追求性能则也可考虑下第三方的concurrent-map。
相关文章:
![](https://img-blog.csdnimg.cn/0a364c09d1d6450f9889d190e0747796.png#pic_center)
map、sync.map、concurrent-map适用场景与源码解析
最近一直加班,无论工作日还是周末,虽然每天很忙但总感觉空空的,很少有时间停下来思考与总结。项目中各种甩锅,最后最苦逼的还是落到了研发的头上,文档编写、环境部署、问题排查虐得一遍又一遍。事情杂乱,研…...
![](https://www.ngui.cc/images/no-images.jpg)
分布式事物实现方案及优缺点
实现分布式事务是一个复杂的任务,涉及到许多技术和概念。在Java开发中,你可以借助一些框架和工具来实现分布式事务,以下是一些常见的方法: 1.两阶段提交(2PC) 这是一种经典的分布式事务处理方法。它涉及到…...
![](https://www.ngui.cc/images/no-images.jpg)
java使用@interface和反射来动态生成excel
1、对象类上搞注解 public class ReportExecuteDetailDto { // 项目信息 private String regionCode; // 大区编号 ExcelColumn(order 0, title "大区") private String regionName; // 大区名称 ExcelColumn(order 14, tit…...
![](https://img-blog.csdnimg.cn/4f888dc8ca004f8a9c8650b6a020691b.png)
【微服务】04-Polly实现失败重试和限流熔断
文章目录 1. Polly实现失败重试1.1 Polly组件包1.2 Polly的能力1.3 Polly使用步骤1.4 适合失败重试的场景1.5 最佳实践 2.Polly实现熔断限流避免雪崩效应2.1 策略类型2.2 组合策略 1. Polly实现失败重试 1.1 Polly组件包 PollyPolly.Extensions.HttpMicrosoft.Extensions.Htt…...
![](https://img-blog.csdnimg.cn/49003d19702f43c9a233b53cf361faf0.png)
如何使用HTML5新增的标签来构建语义化的页面结构?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ <header>:⭐ <nav>:⭐ <main>:⭐ <section>:⭐ <article>:⭐ <aside>:⭐ <footer>:⭐ <figure> 和 &l…...
![](https://img-blog.csdnimg.cn/7209c8f3db49411dbd9b027ffad8b42b.png)
Vmware 虚拟机挂起恢复后发现无法 Ping 通,无法连接到主机
解决办法 进入对应主机中,切换到 root 账户,重启网络服务。 systemctl stop NetworkManager systemctl restart network在网上还找到了另一种解决方法: 在网卡配置文件中增加参数 NM_CONTROLLED"no"。 在 Centos 7 中修改如下所…...
![](https://img-blog.csdnimg.cn/3f127089a14c42f3bcb22da82d23dfab.gif)
Web自动化测试之图文验证码的解决方案
对于web应用程序来讲,处于安全性考虑,在登录的时候,都会设置验证码, 验证码的类型种类繁多,有图片中辨别数字字母的,有点击图片中指定的文字的,也有算术计算结果的,再复杂一点就是滑…...
![](https://www.ngui.cc/images/no-images.jpg)
软考高级系统架构设计师系列论文九十:论分布式数据库的设计与实现
软考高级系统架构设计师系列论文九十:论分布式数据库的设计与实现 一、分布式数据库相关知识点二、摘要三、正文四、总结一、分布式数据库相关知识点 软考高级系统架构设计师系列之:分布式存储技术...
![](https://www.ngui.cc/images/no-images.jpg)
Day 84:网络结构与参数
单层数据 package dl;/*** One layer, support all four layer types. The code mainly initializes, gets,* and sets variables. Essentially no algorithm is implemented.*/ public class CnnLayer {/*** The type of the layer.*/LayerTypeEnum type;/*** The number of …...
![](https://img-blog.csdnimg.cn/66433ef94b464db28d5d6d25a225f0d4.png)
vue2.6及以下版本导入 TDesign UI组件库
TDesign 官方文档:https://tdesign.tencent.com/vue/components/button 我们先打开一个普通的vue项目 然后 如果你是 vue 2.6 或者 低于 2.6 在终端执行 npm i tdesign-vue如果你是 2.7 或者更高 执行 npm i tdesign-vuenaruto这里 我们 以 2.6为例 因为大部分人 用vue2 都是…...
![](https://img-blog.csdnimg.cn/f9422ee969224bea900c43b934550f8d.png)
VR/AR/眼镜投屏充电方案(LDR6020)
VR眼镜即VR头显,也称虚拟现实头戴式显示设备,随着元宇宙概念的传播,VR眼镜的热度一直只增不减,但是头戴设备的续航一直被人诟病,如果增大电池就会让头显变得笨重影响体验,所以目前最佳的解决方案还是使用VR…...
![](https://img-blog.csdnimg.cn/bafacf122e5d49b88436d803ce010ffd.png)
区分什么是Java内存模型(JMM)和 JVM运行时数据区
文章目录 一、概念区分1、什么是内存模型?什么是(内存区域)运行时数据区?2、为什么要有Java内存模型?2.1、硬件的效率与一致性2.2、 CPU和缓存的一致性2.2.1、为什么需要CPU cache?2.2.2、三级缓存…...
![](https://img-blog.csdnimg.cn/35ce2c3c19654e17b09b05da497b4aed.png)
Flask狼书笔记 | 04_表单
文章目录 4 表单4.1 HTML表单4.2 使用Flask-WTF4.3 处理表单数据4.4 表单进阶实践小记 4 表单 表单是和用户交互最常见的方式之一,本章涉及的Python包由WTForms、Flask-WTF、Flask-CKEditor。(p104) 4.1 HTML表单 通过<form>标签创建…...
![](https://img-blog.csdnimg.cn/d6ee65d3d8094fd383133d21aae9980b.png)
RabbitMQ+springboot用延迟插件实现延迟消息的发送
延迟队列:其实就是死信队列中消息过期的特殊情况 延迟队列应用场景: 可以用死信队列来实现,不过死信队列要等上一个消息消费成功,才会进行下一个消息的消费,这时候就需要用到延迟插件了,不过要线在docker上…...
![](https://img-blog.csdnimg.cn/9d85f8e2a4bb4fa2b39a843e1d27fad5.png)
多线程和并发(1)—等待/通知模型
一、进程通信和进程同步 1.进程通信的方法 同一台计算机的进程通信称为IPC(Inter-process communication),不同计 算机之间的进程通信被称为 RPC(Romote process communication),需要通过网络,并遵守共同的协议。**进…...
![](https://img-blog.csdnimg.cn/1b7af88bf3d84496a9c0b2ed47760c56.png)
浏览器的事件循环
其实在我们电脑的操作系统中,每一个运行的程序都会由自己的进程(可能是一个,也可能有多个),浏览器就是一个程序,它的运行在操作系统中,拥有一组自己的进程(主进程,渲染进…...
![](https://img-blog.csdnimg.cn/1ed65b5d3873496d8d1af6673d1e5d79.png)
跳跃游戏 II【贪心算法】
跳跃游戏 II class Solution {public int jump(int[] nums) {int cur 0;//当前最大覆盖路径int next 0;//下一步的最大覆盖路径int res 0;//存放结果,到达终点时最少的跳跃步数for (int i 0; i < nums.length; i) {//遍历数组,以给出数组以一个…...
![](https://img-blog.csdnimg.cn/d7f311be9a0240a39ba56c347925701f.png)
promise
promise 属于事件循环的微任务,具体详见:事件循环 Promise 语法: const p1 new Promise((reslove,reject)>{console.log(2);reslove(1) }).then((data)>{console.log(3);console.log(data) }).catch((data)>{console.log(3); }) promise.th…...
![](https://www.ngui.cc/images/no-images.jpg)
前端面试:【网络协议与性能优化】HTTP/HTTPS、TCP/IP和WebSocket
嗨,亲爱的Web开发者!在构建现代Web应用时,了解网络协议是优化性能和确保安全性的关键。本文将深入探讨HTTP/HTTPS、TCP/IP和WebSocket这三个网络协议,帮助你理解它们的作用以及如何优化Web应用的性能。 1. HTTP/HTTPS协议…...
![](https://img-blog.csdnimg.cn/69d49d9d978c45c88eaf8011c9f7c2d5.png)
设计模式之工厂模式(万字长文)
文章目录 概述工厂模式的优点包括工厂模式有几种主要的变体看一个具体需求使用传统的方式来完成传统的方式的优缺点 简单工厂模式基本介绍使用简单工厂模式简单工厂模式的优缺点优点:缺点: 工厂方法模式看一个新的需求思路 1思路 2工厂方法模式介绍工厂方…...
![](https://img-blog.csdnimg.cn/img_convert/b487131d7c48aedfbb84b0ddce347751.png)
CNN 02(CNN原理)
一、卷积神经网络(CNN)原理 1.1 卷积神经网络的组成 定义 卷积神经网络由一个或多个卷积层、池化层以及全连接层等组成。与其他深度学习结构相比,卷积神经网络在图像等方面能够给出更好的结果。这一模型也可以使用反向传播算法进行训练。相比较其他浅层或深度神经…...
![](https://img-blog.csdnimg.cn/1c6538fa6d744154b26c81b080015465.png#pic_center)
Android View动画整理
View 动画相关内容可参考官网 动画资源 此前也有写 View 动画相关的内容,但都只是记录代码,没有特别分析。以此篇作为汇总、整理、分析。 Android View 动画有4中,分别是 平移动画 TranslateAnimation缩放动画 ScaleAnimation旋转动画 Rot…...
![](https://img-blog.csdnimg.cn/918d36292cd64119b0745df0d59678b7.png)
阿里云架构
负载均衡slb 分类以及应用场景 负载均衡slb clb 传统的负载均衡(原slb) 支持4层和7层(仅支持对uri(location),域名进行转发) 一般使用slb(clb) alb 应用负载均衡 只支持7层,整合了nginx负载均衡的各种功能,可以根据用户请求头,响应头 如果需要详细处理用户请求(浏…...
![](https://img-blog.csdnimg.cn/ed1ee60c4f8849d0bcb4f09c2f2aaad6.png#pic_center)
【C语言】操作符大全(保姆级介绍)
🚩纸上得来终觉浅, 绝知此事要躬行。 🌟主页:June-Frost 🚀专栏:C语言 🔥该篇将详细介绍各种操作符的功能。 目录: 📘 前言① 算术操作符②移位操作符③位操作符④赋值操…...
![](https://img-blog.csdnimg.cn/45475b0c74d843e5aade8b36cc07eb91.png)
ruoyi-cloud部署
默认你已经安装mysql,nacos,seata,sentinel等(没有的可以先找教程安装) 1、下载源码:git clone https://gitee.com/zhangmrit/ruoyi-cloud 2、项目依赖导入,选择自己的maven环境等,创…...
![](https://img-blog.csdnimg.cn/img_convert/818713cdbdeec47d055d3154704ec077.png#?w=591&h=774&e=png&b=202023)
Vue3(开发h5适配)
在开发移动端的时候需要适配各种机型,有大的,有小的,我们需要一套代码,在不同的分辨率适应各种机型。 因此我们需要设置meta标签 <meta name"viewport" content"widthdevice-width, initial-scale1.0">…...
![](https://www.ngui.cc/images/no-images.jpg)
图的存储:邻接矩阵法
1.邻接矩阵的实现 邻接矩阵的定义:在无向图和有向图中,使用二维数组表示各个顶点的相邻情况:1代表相邻,0表示不相邻。 代码实现: #define MaxVertexNum 100//顶点数目的最大值 typedef struct {char Vex [MaxVertexN…...
![](https://www.ngui.cc/images/no-images.jpg)
如何优雅的使用Git?
第一部分:Git的基本概念和初始设置 Git是一个分布式版本控制系统,它允许多人共同工作,同时跟踪和管理项目的版本历史。使用Git,您可以恢复旧版本、创建新分支进行实验,并与其他开发者进行协作,而不会影响主…...
![](https://img-blog.csdnimg.cn/ecb6f59e2a424ac4b7b4c3ffc76e976f.png)
【【STM32分析IO该设置什么模式的问题】】
STM32分析IO该设置什么模式的问题 我们分析而言 我们对于PA0 的设计就从此而来 对于边沿触发的选择我们已经有所了解了 我们下拉,但是当我们摁下开关的时候 从0到1 导通了 所以这个是下拉 上升沿触发 而对于KEY0 我们摁下是使得电路从原来悬空高阻态到地就是0 所以…...
![](https://img-blog.csdnimg.cn/6ffe4679a3c0452c915b9a236e4eb0d0.png)
飞天使-k8s基础组件分析-服务与ingress
文章目录 服务的介绍服务代理服务发现连接集群外服务服务发布无头服务 服务,pod和dns的关系端口转发通过expose 暴露应用服务案例INGRESSMetalLB使用参考文档 服务的介绍 服务的作用是啥? 提供外部调用,保证podip的真实性看看服务解决了什么…...
![](https://images2018.cnblogs.com/blog/1301126/201803/1301126-20180327005313634-1099059186.png)
网站广告弹窗代码/樱花12e56
HTTP: HTTP的两大特点:无连接无状态 无连接:每次连接只处理一个请求,服务器处理完客户端的请求,响应给客户端之后就断开本次连接(及时的释放系统资源) 无状态:每次请求完之后,服务器…...
![](https://img-blog.csdnimg.cn/img_convert/9c0a1dfc19121c5222dcfb5792d7422d.png)
腾博会的网站是什么/百度关键词seo推广
基础知识 jwt是由三部分构成的,第一部分是头部(header),第二部分是载荷(payload),第三部分为签证(signature) 头部 头部声明了类型和加密方法,如下 {typ:…...
![](https://img-blog.csdnimg.cn/img_convert/77d35851fed7f10a7480cd7c4c64d3cb.png)
网站死链接怎么删除/搜狗站长工具
从Unity 2018.3(TextMeshPro1.4)起,添加了Font Fallback和Dynamic SDF System以实现灵活的SDF使用。有了这个新功能后,我们可以实现以下用法:预先为中文字中经常使用的字符生成普通SDF根据需要追加取得使用频率低的文字…...
![](https://img-blog.csdnimg.cn/20210602194554463.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5ODA1MzYy,size_16,color_FFFFFF,t_70)
做外贸现在一般都通过哪些网站/网络营销方法
最近看完了空间计量经济学的理论部分,因此打算开始学习一下实战,实战所使用的主要是GEODA家族的软件包们,首先还是打算先学习python的pysal包,毕竟还是更喜欢代码,而且相较于GEODA和GEODASPACE,写代码还是会…...
![](/images/no-images.jpg)
网络规划设计师历年真题/西安关键字优化哪家好
学习笔记:java数据结构 第3章-稀疏数组和队列 要求: 在前面的基础上,将稀疏数组保存到磁盘上,比如map.data恢复原来的数组时,读取map.data 进行恢复 package SparseArray; import java.awt.*; import java.io.*;pub…...
![](https://img-blog.csdnimg.cn/img_convert/f222166c6a8d9d2a8b6d23f90f7d1663.png)
phpcms律师网站源码/免费的app推广平台
题库来源:安全生产模拟考试一点通公众号小程序 2022安全员-A证试题是安全员-A证模拟考试题库的多种练习模式!2022年安全员-A证特种作业证考试题库及答案依据安全员-A证新考试大纲。安全员-A证全部考试题库随时根据安全生产模拟考试一点通提高考试通过率…...