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 读取数据,并转换readonly
	e, ok := read.m[key]
	if !ok && read.amended { // readonly没有找到对应数据
		m.mu.Lock()
        // 双重检测:
        // 再检查一次readonly,以防中间有Map.dirty被替换为readonly
		read, _ = 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+1
	if m.misses < len(m.dirty) {
		return
	}
    // 当misses等于dirty的长度,m.dirty转换readOnly,amended被默认赋值成false  
	m.read.Store(readOnly{m: m.dirty})
	m.dirty = nil
	m.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被替换为readonly
	read, _ = 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中。)

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

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

相关文章

Flink1.17实战教程(第七篇:Flink SQL)

系列文章目录 Flink1.17实战教程&#xff08;第一篇&#xff1a;概念、部署、架构&#xff09; Flink1.17实战教程&#xff08;第二篇&#xff1a;DataStream API&#xff09; Flink1.17实战教程&#xff08;第三篇&#xff1a;时间和窗口&#xff09; Flink1.17实战教程&…

2023年12月27日学习记录_加入噪声

目录 1、今日计划学习内容2、今日学习内容1、add noise to audio clipssignal to noise ratio(SNR)加入 additive white gaussian noise(AWGN)加入 real world noises 2、使用kaggel上的一个小demo&#xff1a;CNN模型运行时出现的问题调整采样率时出现bug 3、明确90dB下能否声…

[递归回溯枚举] 装载问题

装载问题 题目描述 有一批共 n 个集装箱要装上 2 艘载重量分别为 c1和 c2的轮船&#xff0c;其中集装箱 i 的重量为 wi&#xff0c;且 装载问题要求确定&#xff0c;是否有一个合理的装在方案可将这 n 个集装箱装上这 2 艘轮船。如果有&#xff0c;找出最优装载方案。 关于输…

14 Arbitration in sequencer(仲裁)

uvm_sequencer 有一个内置机制&#xff0c;可以在sequencer上同时运行的sequence中进行仲裁。基于仲裁算法&#xff0c;sequencer将得到仲裁权的sequence的sequence_item发送到driver。 每个sequence发送的sequence_items也有自己的id来区别于其他sequence。 要设置特定的仲裁…

Apipost-Helper使用流程

Apipost-Helper是由Apipost推出的IDEA插件&#xff0c;写完接口可以进行快速调试&#xff0c;且支持搜索接口、根据method跳转接口&#xff0c;还支持生成标准的API文档&#xff0c;注意&#xff1a;这些操作都可以在代码编辑器内独立完成&#xff0c;非常好用&#xff01;这里…

JavaWeb——监听器Listener 过滤器Filter——韩顺平学习笔记

文章目录 JavaWeb 三大组件之监听器 ListenerListenerJavaWeb 的监听器ServletContextListener 监听器ServletContextAttributeListener 监听器其它监听器-使用较少HttpSessionListener 监听器HttpSessionAttributeListener 监听器ServletRequestListener 监听器ServletRequest…

泰迪智能科技分享:AI大模型发展趋势分析

大规模预训练语言模型&#xff0c;也被称为“大模型”或“基座模型”&#xff0c;其特点在于拥有巨大的参数量&#xff0c;构成了复杂的人工神经网络模型。大模型具有规模性&#xff08;参数量大&#xff09;、涌现性&#xff08;产生预料之外的新能力&#xff09;以及通用性&a…

uni-app condition启动模式配置

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

Java EE 网络原理之HTTP 响应详解

文章目录 1. 认识"状态码"(status code)2. 通过 form 表单构造 HTTP 请求3. 通过 ajax 构造 HTTP 请求 1. 认识"状态码"(status code) 表示了这次请求对应的响应&#xff0c;是什么样的状态 &#xff08;成功&#xff0c;失败&#xff0c;其他的情况&…

Graph Transformer2023最新研究成果汇总,附15篇必看论文

图Transformer是一种结合了Transformer模型和图神经网络&#xff08;GNN&#xff09;的框架&#xff0c;用于在图形结构数据上执行预测任务。在图Transformer中&#xff0c;Transformer的自注意力机制被用来学习节点之间的关系&#xff0c;而GNN则被用来生成节点的嵌入表示。通…

数据结构与算法(C语言版)P10——图

1、图的基本概念和术语 前面学过&#xff1a; 线性是一对一树形是一对多 而今天要学习的图形结构是多对多。 图的定义&#xff1a; G(V,E) V&#xff1a;顶点(数据元素)的__有穷非空__集合。E&#xff1a;边的有穷集合。 __有向图&#xff1a;__每条边都是有方向的 __无…

【linux】touch的基本使用

碎碎念 刚接触linux时候的几个最基础的命令之一&#xff0c;用来创建文件。如果使用touch --help的时候会发现作者对于touch的简介&#xff1a;Update the access and modification times of each FILE to the current time.用于修改文件的访问和时间戳 带我的leader属于那种…

rsync的介绍与使用

rsync的介绍与使用 一、简介 rsync&#xff08;remote synchronize&#xff09;是Liunx/Unix下的一个远程数据同步工具。它能够以非常高效的方式传输和同步文件&#xff0c;它可以将一个目录的文件快速地同步到另一个目录&#xff0c;还可以通过网络快速同步多台主机间的文件…

使用Python Flask搭建一个简单的Web站点并发布到公网上访问

文章目录 前言1. 安装部署Flask并制作SayHello问答界面2. 安装Cpolar内网穿透3. 配置Flask的问答界面公网访问地址4. 公网远程访问Flask的问答界面 前言 Flask是一个Python编写的Web微框架&#xff0c;让我们可以使用Python语言快速实现一个网站或Web服务&#xff0c;本期教程…

springBoot整合redis做缓存

一、Redis介绍 Redis是当前比较热门的NOSQL系统之一&#xff0c;它是一个开源的使用ANSI c语言编写的key-value存储系统&#xff08;区别于MySQL的二维表格的形式存储。&#xff09;。和Memcache类似&#xff0c;但很大程度补偿了Memcache的不足。和Memcache一样&#xff0c;R…

TDengine 公布 2023 年发展“成绩”,六大亮点引人瞩目

今天&#xff0c;我们进行了 2023 年重大成就和发展成绩盘点&#xff0c;主要归纳为产品创新、市场发展、开源社区、生态建设、活动布道与奖项荣誉六大维度。在元旦前夕&#xff0c;我们也想把这份“2023 年成绩单”分享给所有关注 TDengine 的朋友们。 在今年&#xff0c;最值…

你好!Apache Seata

北京时间 2023 年 10 月 29 日&#xff0c;分布式事务开源项目 Seata 正式通过 Apache 基金会的投票决议&#xff0c;以全票通过的优秀表现正式成为 Apache 孵化器项目&#xff01; 根据 Apache 基金会邮件列表显示&#xff0c;在包含 13 个约束性投票 (binding votes) 和 6 个…

百分点科技成为中国“数据要素×”生态合作伙伴

12月24日&#xff0c;由中国经济体制改革研究会、中国电子、郑州市人民政府、中国经济改革研究基金会联合主办的中国“数据要素”生态大会在郑州召开&#xff0c;百分点科技受邀出席&#xff0c;并获颁中国“数据要素x”2024年度生态伙伴合作证书。 大会邀请了国家数据局党组成…

华天动力OA TemplateService 任意文件读取漏洞复现

0x01 产品简介 华天动力OA是一款将先进的管理思想、 管理模式和软件技术、网络技术相结合&#xff0c;为用户提供了低成本、 高效能的协同办公和管理平台。 0x02 漏洞概述 华天动力OA TemplateService接口处存在任意文件读取漏洞&#xff0c;未经身份认证的攻击者可利用此漏洞…

边缘计算网关:在智慧储能系统中做好储能通信管家

背景 目前储能系统主要由储能单元和监控与调度管理单元组成&#xff0c;储能单元包含储能电池组(BA)、电池管理系统(BMS)、储能变流器(PCS)等&#xff1b;监控与调度管理单元包括中央控制系统(MGCC)、能量管理系统(EMS)等。 2021年8月&#xff0c;国家发改委发布《电化学储能…