concurrent-map 和 sync.Map,我该选择哪个?

news/2024/4/26 17:01:57/文章来源:https://blog.csdn.net/ma_nong33/article/details/129137421

官方的map并不是线程安全的,如果我们在多线程中并发对一个map进行读写操作,是会引发panic的。解决方案除了使用锁来对map进行保护外,还有两种方式:

一,开源项目 concurrent-map 提供了可以用来做并发安全的map

二,Go1.9之后,标准库提供了一个sync.Map

这两种并发安全的map,我们应该怎么选择呢?

在concurrent-map我看到这么一段话:

标准库中的sync.Map是专为append-only场景设计的。因此,如果您想将Map用于一个类似内存数据库,那么使用我们的版本可能会受益。你可以在golang repo上读到更多,这里 and 这里 译注:sync.Map在读多写少性能比较好,否则并发性能很差

concurrent-map为什么会有这种表述呢?这篇文章就来庖丁解牛下。

concurrent-map

concurrent-map是Golang中一个流行的并发安全的哈希表库,它允许多个goroutine同时对哈希表进行读写操作,而不需要使用显式的锁或同步原语。

该库的核心原理是使用分片锁,将哈希表分成多个小的哈希表片段,并为每个片段分配一个独立的锁。当多个goroutine尝试同时读写同一个片段时,只有该片段上的锁会被锁住,而其他片段的锁则不受影响,从而避免了整个哈希表被锁住的情况。

当进行写操作时,只需要锁住要写入的片段的锁,以确保原子性操作。当进行读操作时,则不需要锁住片段的锁,只需要对该片段上的读取操作进行同步即可。

此外,concurrent-map库还使用了一些优化策略,如缓存哈希值和桶的地址,以减少计算和查找时间,从而提高并发读写性能。

总之,concurrent-map库的原理是基于分片锁和其他优化策略来实现高效的并发安全哈希表。

我们先看它的使用方式:

	// 创建一个新的 map.m := cmap.New[string]()// 设置变量m一个键为“foo”值为“bar”键值对m.Set("foo", "bar")// 从m中获取指定键值.bar, ok := m.Get("foo")// 删除键为“foo”的项m.Remove("foo")

它的New方法创建了一个ConcurrentMap结构

typeConcurrentMap[K comparable, V any] struct {shards   []*ConcurrentMapShared[K, V]shardingfunc(keyK) uint32
}

我们看ConcurrentMap结构中的shards,是用来代表map分片之后的这些存储分片ConcurrentMapShared。

而sharing这个匿名函数代表的是分配的hash函数。

而存储分片是一个基础的,带有互斥锁的map

typeConcurrentMapShared[K comparable, V any] struct {itemsmap[K]Vsync.RWMutex 
}

所以看到这里我们其实心里明白了个七七八八了,再看下它的New/Set/Get的流程如下:

是的,基本原理就是如上图所示。concurrent-map就是将一个大map拆分成若干个小map,然后用若干个小mutex 对这些小map进行保护。这样,通过降低锁的粒度提升并发程度。毕竟嘛,一个诸葛亮不如十个臭皮匠。

sync.Map

sync.Map是Golang标准库中提供的一个并发安全的哈希表,它与常规的map相比,可以在多个goroutine并发访问时,保证数据的安全性和一致性。

理解sync.Map,最关键就是理解Map结构。

type Map struct {mu Mutex //互斥锁,用于锁定dirty map//优先读map,支持原子操作,注释中有readOnly不是说read是只读,而是它的结构体。read实际上有写的操作read atomic.Value // readOnly// dirty是一个当前最新的map,允许读写dirty map[any]*entry// 主要记录read读取不到数据加锁读取readmap以及dirty map的次数,当misses等于dirty的长度时,会将dirty复制到readmisses int
}

这里的sync.Map的逻辑还是比较复杂的。我们再看它的Store函数和Load函数。

func (m *Map) Store(key, value any) 
func (m *Map) Load(key any) (value any, ok bool) 

我们先把Store的代码流程图画出来

我们看下,这里面有几个步骤是非常有细节的。

首先,第一次判断read中是否有key的时候是没有加锁的,所以当第一次判断结束后,一旦明确read中没有key,要做后续的操作之前,先做一次加锁操作,做完加锁操作之后,又判断了一次key是否在read中。这是为什么呢?其实是由于在加锁这个操作的前后,map还是有可能有变化的,人不可能两次踏入同一个河流,map也不可能在加锁前后两次都不变,所以这里必须进行二次判断,这里可以说是非常细节了。

其次,在判断read或者dirty中已经有key的时候,Store做的操作不是复制一份value到目标结构,而是使用原子替换atomic.StorePointer 来将目标map中key对应的value指针替换为参数value。为什么呢? - 这是极致的性能优化写法,原子替换能减少一次值拷贝操作,做一次指针赋值就能替换拷贝内存操作。从这里我们也能理解为什么这个并发map会放在atomic包中,因为它的实现大量依赖atomic的原子操作。

同样,我们将Load的代码转化为流程图如下,

从Load中我们大致能看出sync.Map的思路。

sync.Map内部使用两个map,read和dirty。其实read的map的作用是挡在读写操作的第一个屏障。如果读写在这个read中能直接操作的话,我们就直接在read中读写,那么就可以完全避免使用锁,性能自然就提升了。

而dirty的作用就相当于是一个缓冲区,一旦要写的key在read中找不到,我们就会先写dirty中。这个好处是什么?也是不去影响读read的操作,不会出现并发读写一个数据结构的情况。

而什么时候dirty的缓存清空同步到read中呢?就是“当map的miss标记大于dirty的个数的时候”。

这里我读的时候也确实有这个疑问,为什么是“当miss标记个数大于dirty个数”。而不是当miss标记个数大于某个值呢?我是这么理解,miss是代表读操作在read中失效的数量,而dirty个数代表写操作在read中失效的数量。如果使用固定值来比对miss个数,那么这个固定值是不好定的,比如一个有10个key的map和一个有10000个key的map如果都是一样的固定值,那是明显不合适的。所以就找了这么个“浮动阈值”。

concurrent-map和sync.map的比较

我们再回到最开始的那一段话:

标准库中的sync.Map是专为append-only场景设计的。因此,如果您想将Map用于一个类似内存数据库,那么使用我们的版本可能会受益。你可以在golang repo上读到更多,这里 and 这里 译注:sync.Map在读多写少性能比较好,否则并发性能很差

通过以上的代码分析,我们看出sync.Map的这个机制,是一个想追求无锁读写的结构,它最好的运行方式是读永远都命中read,写只命中dirty,这用能不用任何锁机制就能做到map读写。而它最差的运行状态是read和dirty不断做替换和清理动作,性能就无法达到预期。而什么时候可能出现最差运行状态呢?- 大量的写操作和大量的读操作。大量读写会导致“map的miss标记大于dirty的个数”。 这个时候sync.Map中第一层屏障会失效,dirty就会频繁变动。

而current-map就相当于是一个比较中等中规中矩的方案。它的每次读写都会用到锁,只是这个锁的粒度比较小。它的最优运行方式是我们的所有并发读写都是分散在不同的hash切片中。它的最差运行方式就是我们所有的并发读写都集中在一个hash切片。但是按照实际运行逻辑,这两种极端情况都不会发生。

所以总结下来,concurrent-map 的这段话确实没有骗我们:

sync.Map在读多写少性能比较好,而concurrent-map 在key的hash度高的情况下性能比较好。

在无法确定读写比的情况下,建议使用 concurrent-map。

最后说一句:世上本没有烦恼,选择多了,便有了幸福的烦恼。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_72017.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端页面jquery规范写法

使用最新版本的 jQuery 最新版本的 jQuery 会改进性能和增加新功能,若不是为了兼容旧浏览器,建议使用最新版本的 jQuery。以下是三条常见的 jQuery 语句,版本越新,性能越好: $(.elem) $(.elem, context) context.find(.elem) 结果 1.6.2 版执行次数远超两个老版本。 jQ…

一文解决Rust字符串:String,str,String,str,CString,CStr

一、str和&str和String的区别 1.存放位置,可变不可变: str来源于Rust本身的数据类型,而String类型来自于标准库。首先看一下str 和 String之间的区别:String是一个可变的、堆上分配的UTF-8的字节缓冲区。而str是一个不可变的…

ElementUI分页的实现

官网地址&#xff1a;Element - The worlds most popular Vue UI framework 第一步&#xff1a;拷贝你喜欢的分页类型放在你的组件页面需要用到的分页位置 <el-paginationsize-change"handleSizeChange"current-change"handleCurrentChange":current-p…

记一次:request请求总结

前言&#xff1a;和前端联调的时候发现前端人员请求的方式不对&#xff0c;固做此总结问题&#xff1a;request请求方式有多少种&#xff1f;答&#xff1a;Java后端查看有8种&#xff0c;spring-web中的java枚举图如下而使用PostMan查看有15种&#xff0c;如下图GET&#xff0…

【重点掌握】Java基础之Javaweb核心技术详解

都说一入Java深似海&#xff0c;从此代码是爱人&#xff0c;但是学习的过程却从来都不轻松。当下&#xff0c;越来越多的互联网企业&#xff0c;招聘Java工程师时&#xff0c;明确写道需熟练掌握JavaWeb技术。作为衔接前后端的重要一环&#xff0c;JavaWeb技术已成为程序员向大…

火热报名 | DockQuery 1.2 beta版本体验官开启招募!

DockQuery是什么&#xff1f; DockQuery 代号「天狼」&#xff0c;是图尔兹全新自研的一款专业新型数据库桌面客户端&#xff0c;专为信创背景下国内外数据库开发/管理而设计&#xff0c;全面覆盖信创数据库目录、支持国内外操作系统。 目前&#xff0c;DockQuery 仅以社区版…

【教程】GitBook Editor编写电子书

GitBook Editor电子书编写说明1、安装软件2、创建文档3、编辑文档4、生成电子书1、安装软件 下载并安装GitBook Editor软件&#xff0c;网上资源很多&#xff0c;根据自己系统选用即可 官网参考&#xff1a;GitBook - Where technical teams document. 2、创建文档 1&#xf…

Talk | 清华大学交叉信息研究院助理教授杜韬:利用计算方法探究流固耦合

本期为TechBeat人工智能社区第474期线上Talk&#xff01; 北京时间2月15日(周三)20:00&#xff0c;清华大学交叉信息研究院助理教授——杜韬的Talk将准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “利用计算方法探究流固耦合”&#xff0c;届时将介绍流固…

HTTP与HTTPS原理

目录 HTTP 协议背景 协议格式 请求行 请求报头 请求正文&#xff08;有效载荷&#xff09; 响应行 HTTPS原理 协议背景 什么是加密&#xff1f; 为什么要加密&#xff1f; 加密方式 数据摘要&#xff08;数据指纹&#xff09; 协议加密方案实现探究 方案一&#xff1a;只使用对…

【云原生】初识Kubernetes的理论基础

一、kubernetes概述 1.1 kubernetes介绍 K8S的全称为Kubernetes (K12345678S)&#xff0c;首字母与尾字母中间有8个字母&#xff0c;缩写为K8S 作用 用于自动部署、扩展和管理“容器化(containerized) 应用程序”的开源系统。可以理解成K8S是负责自动化运维管理多个容器化程序…

【云原生】k8s之Yaml文件详解

一、K8S支持的文件格式 kubernetes支持YAML和JSON文件格式管理资源对象。 JSON格式&#xff1a;主要用于api接口之间消息的传递YAML格式&#xff1a;用于配置和管理&#xff0c;YAML是一种简洁的非标记性语言&#xff0c;内容格式人性化&#xff0c;较易读 1、yaml和json的主…

[Golang实战]github.io部署个人博客hugo[新手开箱可用][小白教程]

[Golang实战]github.io部署个人博客hugo[新手开箱可用][小白教程]1.新手教程(小白也能学会)2.开始准备2.1myBlog是hugo的项目1.安装Hugo2.创建hugo项目2.2 xxxx.github.io是github.io中规定的pages项目3.成功部署4.TODO自动化workflows部署github.io1.新手教程(小白也能学会) …

分析| 2023年移动开发平台的发展空间

春节过后返工已经过月&#xff0c;许多移动开发领域的企业都在忙着做技术调研与选型。在此之前&#xff0c;不如先回顾一下2022年的市场趋势&#xff0c;再结合好的移动开发平台的标准&#xff0c;从中窥见2023年的发展前景。 Gartner十大战略技术趋势 全球权威咨询机构Gartne…

分析称勒索攻击在非洲、中东与中国增长最快

Orange Cyberdefense&#xff08;OCD&#xff09;于 2022 年 12 月 1 日发布了最新的网络威胁年度报告。报告中指出&#xff0c;网络勒索仍然是头号威胁 &#xff0c;也逐渐泛滥到世界各地。 报告中的网络威胁指的是企业网络中的某些资产被包括勒索软件在内的攻击进行勒索&…

2022-06-16_555时基的迷人历史和先天缺陷!

https://www.eet-china.com/news/magazine220608.html 555时基的迷人历史和先天缺陷&#xff01; 发布于2022-06-16 03:39:12 LARRY STABILE 流行数十年的555时基&#xff0c;业内不知晓的工程师应该寥寥无几&#xff01;几乎所有的数字电路教材中&#xff0c;都有该芯片的身影…

LeetCode 周赛 333,你管这叫 Medium 难度?

本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 提问。 大家好&#xff0c;我是小彭。 上周是 LeetCode 第 333 场周赛&#xff0c;你参加了吗&#xff1f;这场周赛质量很高&#xff0c;但难度标得不对&#xff0c;我真的会谢。算法…

基于龙芯 2K1000 的嵌入式 Linux 系统移植和驱动程序设计

2.1 需求分析 本课题以龙芯 2K1000 处理器为嵌入式系统的处理器&#xff0c;需要实现一个完成的嵌 入式软件系统&#xff0c;系统能够正常启动并可以稳定运行嵌入式 Linux。设计网络设备驱 动&#xff0c;可以实现板卡与其他网络设备之间的网络连接和文件传输。设计 PCIE 设备驱…

重温一下C#的时间类型,并简单写一个定时器功能

&#x1f389;&#x1f389; 时间是一个非常抽象的概念&#xff0c;本篇文章我们不深究目前电脑上的时候是如何保持全网同步。主要是讲讲在使用C#编程语言里的时间类型。最后使用定时任务简单写一个提醒功能&#xff0c;比如&#xff1a;每天10点准时打开一次csdn首页&#xff…

yolov5源码解读--数据处理模块

yolov5源码解读--数据处理模块加载数据读取图片加载标签马赛克数据增强图片标签其他的数据增强变图像变标签__getitem__构建Batch加载数据 create_dataloader 跳转到datasets.py文件中&#xff0c;可以看到支持输入的文件类型非常丰富。。 回归正题 跳转LoadImagesAndLabel…

分析JEP 290机制的Java实现

简介 https://openjdk.org/jeps/290 Filter Incoming Serialization Data过滤传入的序列化数据 JEP290是Java官方提供的一套来防御反序列化的机制&#xff0c;其核心在于提供了一个ObjectInputFilter接口&#xff0c;通过设置filter对象&#xff0c;然后在反序列化&#xff…