golang sync.Map之如何设计一个并发安全的读写分离结构?

在 golang中,想要并发安全的操作map,可以使用sync.Map结构,sync.Map 是一个适合读多写少的数据结构,今天我们来看看它的设计思想,来看看为什么说它适合读多写少的场景。

如下,是golang 中sync.Map的数据结构,其中 属性read 是 只读的 map,dirty 是负责写入的map,sync.Map中的键值对value值本质上都是entry指针类型,entry中的p才指向了实际存储的value值

// sync.Map的核心数据结构
type Map struct {
    mu Mutex                        // 对 dirty 加锁保护,线程安全
    read atomic.Value                 // read 只读的 map,充当缓存层
    dirty map[interface{}]*entry     // 负责写操作的 map,当misses = len(dirty)时,将其赋值给read
    misses int                        // 未命中 read 时的累加计数,每次+1
}
// 上面read字段的数据结构
type readOnly struct {
    m  map[interface{}]*entry // 
    amended bool // Map.dirty的数据和这里read中 m 的数据不一样时,为true
}

// 上面m字段中的entry类型
type entry struct {
    // value是个指针类型
    p unsafe.Pointer // *interface{}
}

我们从一个sync.Map的数据写入和数据查询 两个过程来分析这两个map中数据的变化。

我将不展示具体的代码,仅仅讲述数据的流动,相信懂了这个以后再去看代码应该不难。

步骤一: 首先是一个初始的sync.Map 结构,我们往其中写入数据,数据会写到dirty中,同时,由于sync.Map 刚刚创建,所以read map还不存在,所以这里会先初始化一个read map 。amended 是read map中的一个属性,为true代表 dirty 和read中数据不一致。

image.png

步骤二: 接着,如果后续再继续写入新数据,
在read map没有从dirty 同步数据之前,即amended 变为false之前,再写入新键值对都只会往dirty里写。

image.png

步骤三: 如果有读操作,sync.Map 都会尽可能的让其先读read map,read map读取不到并且amended 为true,即read 和dirty 数据不一致时,会去读dirty,读dirty的过程是上锁的。

image.png

步骤四: 当读取read map中miss次数大于等于dirty数组的长度时,会触发dirty map整体更新为readOnly map,并且这个过程是阻塞的。更新完成后,原先dirty会被置为空,amended 为false,代表read map同步了之前所有的数据。如下图所示,

image.png

整体更新的逻辑是直接替换变量的值,并非挨个复制,

func (m *Map) missLocked() {
    m.misses++
    if m.misses < len(m.dirty) {
        return
    }
    
    // 将dirty置给read,因为穿透概率太大了(原子操作,耗时很小)
    m.read.Store(readOnly{m: m.dirty})
    m.dirty = nil
    m.misses = 0
}

步骤五: 如果后续sync.Map 不再插入新数据,那么读取时就可以一直读取read map中的数据了,直接读取read map 中的key是十分高效的,只需要用atomic.Load 操作 取到readOnly map结构体,然后从中取出特定的key就行。

如果读miss了,因为没有插入新数据,read.amended=false 代表read 是保存了所有的k,v键值对,读miss后,也不会再去读取dirty了,也就不会有读dirty加锁的过程。

// 上面read字段的数据结构
type readOnly struct {
    m  map[interface{}]*entry // 
    amended bool // Map.dirty的数据和这里read中 m 的数据不一样时,为true
}

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    // 因read只读,线程安全,优先读取
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    
    // 如果read没有,并且dirty有新数据,那么去dirty中查找(read.amended=true:dirty和read数据不一致)
    // 暂时省略 后续代码
    .......
	
    }

上面的获取key对应的value过程甚至比RWMutex 读锁下获取map中的value还要高效,毕竟RWmutex 读取时还需要加上读锁,其底层是用atomic.AddInt32 操作,而sync.Map 则是用 atomic.load 获取map,atomic.AddInt32 的开销比atomic.load 的开销要大。

📢📢📢,所以,为什么我们说golang的sync.Map 在大量读的情况下性能极佳,因为在整个读取过程中没有锁开销,atomic.load 原子操作消耗极低。

但是如果后续又写入了新的键值对数据,那么 dirty map中就会又插入到新的键值对,dirty和read的数据又不一致了,read 的amended 将改为true。

并且由于之前dirty整体更新为read后,dirty字段置为nil了,所以,在更改amended时,也会将read中的所有未被删除的key同步到 dirty中

image.png

📢📢📢注意,为什么在dirty整体更新一次read map后,再写入新的键值对时,需要将read map中的数据全部同步到dirty,因为随着dirty的慢慢写入,后续读操作又会造成读miss的增加,最终会再次触发dirty map整体更新为readOnly map,amended 改为false,代表read map中又有所有键值对数据了,也就是会回到步骤三的操作,重复步骤三到步骤五的过程。

image.png

只有将read map中的数据全部同步到dirty ,才能保证后续的整体更新,不会造成丢失数据。

看到这里应该能够明白sync.Map的适合场景了,我来总结下,

sync.Map 适合读多写少的场景,大量的读操作可以通过只读取read map 拥有极好的性能。

而如果写操作增加,首先会造成read map中读取miss增加,会回源到dirty中读取,且dirty可能会频繁整体更新为read,回源读取,整体更新的步骤都是阻塞上锁的。

其次,写操作也会带来dirty和 read中数据频繁的不一致,导致read中的数据需要同步到dirty中,这个过程在键值对比较多时,性能损耗较大且整个过程是阻塞的。

所以sync.Map 并不适合大量写操作。

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

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

相关文章

详细分析Js中的Promise.all基本知识(附Demo)

目录 1. 基本知识2. Demo3. 实战 1. 基本知识 Promise.all 是 JavaScript 中的一个方法&#xff0c;它接受一个由 Promise 对象组成的数组作为参数&#xff0c;并在所有 Promise 对象都变为 resolved&#xff08;已完成&#xff09;状态时才返回一个新的 Promise 对象&#xf…

KTV点歌系统|基于JSP技术+ Mysql+Java+ B/S结构的KTV点歌系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;ssm&#xff0c;springboot的平台设计与实现项目系统开发资源&#xff08;可…

MyBatis是纸老虎吗?(四)

在《MyBatis是纸老虎吗&#xff1f;&#xff08;三&#xff09;》这篇文章中我们一起梳理了MyBatis配置文件的解析流程&#xff0c;并详细介绍了其中的一些常见节点的解析步骤。通过梳理&#xff0c;我们弄清楚了MyBatis配置文件中的一些常用配置项与Java Bean之间的对应关系&a…

linux网线正常,但没有网络,ifconfig没有ip地址

ubuntu 22.04环境&#xff1a; 今天正在用着好好的&#xff0c;不知道为什么突然没有网络了&#xff0c;网线灯也不亮&#xff0c;ifconfig只有lo回环地址。 因为装的双系统&#xff0c;切换到windows环境发现网络是正常的。 使用-a&#xff1a; 使用各种方式比如下面的命令…

大模型应用开发-虚拟人-AI刘能、AI李宏伟

简介 本案例通过python编程调用智谱的大模型接口,以及很简单的prompt设计,实现了用大语言模型模拟一个人物来和我们对话,前端HTML代码是用大语言模型生成的(原因:我根本不会写前端啊~~),本教程适合所有对大模型应用开发感兴趣的初学者,这是个非常有趣的案例。 读完本…

excel 破解 保护工作簿及保护工作表

excel 破解 保护工作簿及保护工作表 对于这种 保护工作簿及保护工作表 不知道密码时&#xff0c;可以使用以下方法破解 保护工作簿破解 打开受保存的excel 右键点击sheet名称 —> 查看代码 复制以下代码&#xff0c;粘贴到代码区域 Sub 工作簿密码破解() ActiveWorkbook.…

C语言例:(m=a==b)||(n=a==b);求解m,n的值

题目&#xff1a;设int a0,b0,m0,n0;执行语句(mab)||(nab);求解m,n的值。 #include<stdio.h> int main(void) {int a0,b0,m0,n0;(mab)||(nab);printf("m%d\n",m);printf("n%d\n",n);return 0; } 优先级: () 优先 优先 a b -->为真&am…

Python元组:不可变的序列

文章目录 一、元组1.创建元组2.访问元组中的元素3.修改元组4.删除元组 二、运算符1.加法运算符2.乘法运算符3.in运算符4.not in运算符 三、元组内置方法1.len()2.max()3.min()4.tuple()4.1 将列表转换成元组4.2 将字符串转换成元组4.3 将集合转换成元组 三、总结 一、元组 在P…

【5G NB-IoT NTN】3GPP R17 NB-IoT NTN介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

10000字!一文学会SQL数据分析

文章来源于山有木兮 原文链接&#xff1a;https://edu.cda.cn/goods/show/3412?targetId5695&preview0 第1节 SQL简介与基础知识 做数据分析的&#xff0c;为什么要写SQL&#xff1f; 没有数据的情况下&#xff0c;我们分析数据就像是巧妇难为无米之炊。因此&#xff0c…

Netty学习——源码篇3 服务端Bootstrap(一) 备份

1 介绍 在分析客户端的代码中&#xff0c;已经对Bootstrap启动Netty有了一个大致的认识&#xff0c;接下来在分析服务端时&#xff0c;就会相对简单。先看一下服务端简单的启动代码。 public class ChatServer {public void start(int port) throws Exception{NioEventLoopGro…

解锁鸿蒙小程序开发新姿势

如今&#xff0c;鸿蒙开发日益受到广大开发者的关注&#xff0c;而小程序开发也早已成为互联网领域的热门话题。那么&#xff0c;我们不禁要问&#xff1a;是否有可能将这两者融为一体&#xff0c;将小程序开发的便捷与高效带入鸿蒙生态中呢&#xff1f;本文将首先带你回顾小程…

SpringCloud alibaba入门简介

SpringCloud alibaba入门简介 1、简介 SpringCloud alibaba官网&#xff1a;SpringCloudAlibaba | Spring Cloud Alibaba (aliyun.com) Spring官网&#xff1a;Spring Cloud Alibaba GitHub中文文档&#xff1a;spring-cloud-alibaba/README-zh.md at 2022.x alibaba/spri…

数据库基本介绍及编译安装mysql

目录 数据库介绍 数据库类型 数据库管理系统&#xff08;DBMS&#xff09; 数据库系统 DBMS的工作模式 关系型数据库的优缺点 编译安装mysql 数据库介绍 数据&#xff1a;描述事物的的符号纪录称为数据&#xff08;Data&#xff09; 表&#xff1a;以行和列的形式组成…

公众号怎么更换主体

公众号账号迁移的作用是什么&#xff1f;只能变更主体吗&#xff1f;1.可合并多个公众号的粉丝、文章&#xff0c;打造超级大V2.可变更公众号主体&#xff0c;更改公众号名称&#xff0c;变更公众号类型——订阅号、服务号随意切换3.可以增加留言功能4.个人订阅号可迁移到企业名…

零知识玩转AVH(8)—— 门槛任务(3)所遇错误及解决(2)

接前一篇文章&#xff1a;零知识玩转AVH&#xff08;7&#xff09;—— 门槛任务&#xff08;2&#xff09;所遇错误及解决&#xff08;1&#xff09; 上一回说到在尝试完成门槛任务 https://github.com/ArmDeveloperEcosystem/Paddle-examples-for-AVH &#xff08;推荐&#…

阿里G6 树状图使用 Iconfont

官网&#xff1a;使用 Iconfont | G6 效果&#xff1a; 完整代码&#xff1a;index.html: <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width…

Python矩阵计算

文章目录 求积求逆最小二乘法特征值 Python科学计算&#xff1a;数组&#x1f4af;数据生成&#x1f4af;数据交互&#x1f4af;微积分&#x1f4af;插值&#x1f4af;拟合&#x1f4af;FFT&#x1f4af;卷积&#x1f4af;滤波&#x1f4af;统计 求积 矩阵是线性代数的核心对…

开发CodeSys可视化控件

文章目录 背景解决方案HTML5 elementsUsing a Visualization as an Element 背景 目前接公司需求&#xff0c;需要开发一套视觉检测系统&#xff0c;并将其集成到codesys中。 编程端基本是采用之前说得的C接口来实现【CodeSys中调用C语言写的动态库】&#xff0c;但是检测画面…

算法笔记p251队列循环队列

目录 队列循环队列循环队列的定义初始化判空判满入队出队获取队列内元素的个数取队首元素取队尾元素 队列 队列是一种先进先出的数据结构&#xff0c;总是从队尾加入元素&#xff0c;从队首移除元素&#xff0c;满足先进先出的原则。队列的常用操作包括获取队列内元素的个数&a…