go语言api源码中文版_Go语言学习——sync.map源码剖析

1.简介
最近看了下Sync包,详读了sync.map源码,感觉源码实现还是比较巧妙的,有不少可以学习的地方;在讲源码前,先看下sync.map的"历史",从网上搜资料,sync.map是Go语言在1.9版本才引入的并发安全的map,对此,有些同学心中可能会有个疑问,如果是支持并发,为什么不采取锁map的方式,为啥还要在单独搞个sync.map结构呢?我们先看下锁map存在的问题:

参考:go语言中文文档:www.topgoer.com

转自:https://studygolang.com/topics/12363#reply0
1)mutex + map
最简单的方案就是在map上加个锁,针对map的所有操作都要提前加锁,其存在问题也很明显,锁竞争会非常频繁;
2)rwmutex + map
优化一点,依据场景,如果是读操作多于写操作,可以把mutex换成rwmutex,相比方案一,有一定优化、至少读读之间不会存在互斥,不过,读写之间还会存在阻塞;
根据锁map的优化迭代方案可知,在读读场景下,rwmutex + map可以并发、不存在阻塞,但是,读写还是存在阻塞,而sync.map要做的事情就是能进一步优化:对于map的各种操作,尽可能不阻塞;为此,sync.map采用了两级缓存实现,一级缓存做无锁并发,二级缓存做有锁并发,如:

97184fa981355a63d2c1c6722eb86b42.png


对上图说明两点:
1)针对sync.map的各种操作,都先经过一级缓存,一级缓存采用无锁的方式,只要不出现击穿,即key都在一级缓存中可以找到,则就不会访问到二级缓存;
2)一级缓存和二级缓存之间存在数据同步,二级缓存数据相对更全一些,所以当一级缓存数据比较久时,可以将二级缓存数据同步一下,该情况是在读击穿时处理;在不击穿的前提下,一级缓存中可能有数据删除,数据移除情况也要同步给二级缓存,清除废弃数据、减少空间占用,该情况是在写击穿并且是一、二级缓存都不存在键的情况处理,总之,同步的原则是:一级缓存数据尽可能新;一级缓存数据只能是二级缓存的子集;

2.实现
sync.map的优势是理想情况下以无锁代替有锁、提高性能,但存在击穿后不得不加锁的问题,一旦击穿进入二级缓存,就要进行锁操作了,所以sync.map不太适用于写多读少以及频繁创建新键的情况;因为要考虑击穿问题,所以sync.map的实现也是围绕击穿考虑的。 2.1读操作
读操作比较简单,步骤是:
1)查看一级缓存中是否有key,有就返回对应value;
2)如果没有则进入读击穿,加锁后,在复看一级缓存中是否有key(复看是因为存在二级缓存向一级缓存同步数据的情况),有就返回对应value;
3)如果没有则看二级缓存中有没有,有就返回对应value,此时出现读击穿,会进入读击穿保护机制——击穿达到一定次数,会将二级缓存数据同步到一级缓存;
需要注意的是,在返回value时要检测value的有效性,如果已经废弃(expunged状态),则不用返回。
2.2写操作
写操作相对复杂,根据key是否存在的情况,可以分为create和update,步骤是:
1)查看一级缓存中是否有key,有就尝试更新,之所以是尝试是因为还要检查数据是否已经废弃,如果已经废弃,即使key在一级缓存中存在,也是击穿效果,因为二级缓存中没有;
2)如果一级缓存操作失败,加锁后,在复看一级缓存,如果有key,则更新value,并检测value是否为废弃状态,如果是,则将key、value写入二级缓存;
3)如果一级缓存中一直没有key,但二级缓存中有,则直接更新数据;
4)如果一级缓存和二级缓存都没有key,则将key、value写入二级缓存,此时会尝试将一级缓存数据同步给二级缓存,用于删除废弃数据(将一级缓存中的删除数据设置为expunged状态),因为只有该情况下,一级缓存数据可能是二级缓存数据的子集,所以当插入全新的key时,才会尝试更新缓存数据、移除废弃数据;
2.3删除操作
删除采取的是延迟删除操作,对于待删除数据,其value先设置为nil,优先从一级缓存删除,如果一级缓存没有,再去二级缓存中删除。
2.4源码
以1.14.4版本为例,处理源码是:

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {    read, _ := m.read.Load().(readOnly)    e, ok := read.m[key]        // 一级缓存没有查到key,加锁、复查,amended用于判断一级缓存和二级缓存是否一致    if !ok && read.amended {        m.mu.Lock()        read, _ = m.read.Load().(readOnly)        e, ok = read.m[key]                // 一级缓存还是没有查到key,则击穿进入二级缓存        if !ok && read.amended {            e, ok = m.dirty[key]            m.missLocked()     // 读击穿保护,根据击穿次数决定是否要同步数据        }        m.mu.Unlock()    }    if !ok {        return nil, false    }    return e.load()}func (m *Map) Store(key, value interface{}) {    read, _ := m.read.Load().(readOnly)    if e, ok := read.m[key]; ok && e.tryStore(&value) {        return    }    m.mu.Lock()    read, _ = m.read.Load().(readOnly)    if e, ok := read.m[key]; ok {        // 一级缓存中有key,则更新value,同时,还要查看value是否已经废弃,如果废弃还要将数据写入二级缓存,确保下次同步前,二级缓存数据的完整性,因为操作到二级缓存,所以需要放在锁操作下;这也是为什么tryStore只是尝试存储        if e.unexpungeLocked() {            m.dirty[key] = e        }        e.storeLocked(&value)    } else if e, ok := m.dirty[key]; ok {        e.storeLocked(&value)    } else {        // 对于key完全不存在的情况,尝试数据同步,从一级缓存到二级缓存        if !read.amended {            m.dirtyLocked()        // 数据同步时,废弃数据不会同步,废弃数据会设置为expunged状态            m.read.Store(readOnly{m: read.m, amended: true})        }        m.dirty[key] = newEntry(value)    }    m.mu.Unlock()}// Delete部分相对简单,主要是将value设置为nilfunc (m *Map) Delete(key interface{}) {    read, _ := m.read.Load().(readOnly)    e, ok := read.m[key]    if !ok && read.amended {        m.mu.Lock()        read, _ = m.read.Load().(readOnly)        e, ok = read.m[key]        if !ok && read.amended {            delete(m.dirty, key)        }        m.mu.Unlock()    }    if ok {        e.delete()    }}func (e *entry) delete() (hadValue bool) {    for {        p := atomic.LoadPointer(&e.p)        if p == nil || p == expunged {            return false        }        if atomic.CompareAndSwapPointer(&e.p, p, nil) {            return true        }    }}

3.总结
sync.map是以无锁操作一级缓存的方式支持并发、提高性能,而根据其实现可知,sync.map适用于读多、更新多、新建少的场景(新建情况下,可能会带来较大的开销,比如:读击穿、数据刚从二级缓存同步到一级缓存后,又要新建key,数据又要反向同步一次)。

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

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

相关文章

怎么成为日上会员直邮_18个日上直邮问题汇总,可以参考一下哦

前段时间发的日上直邮的微头条和视频,很多朋友遇到一些不明白的地方,我把问题汇总了一下,统一回答一下哦其实因为疫情,很多免税店都在做活动,活动方式不太一样,有好多种,我跟大家分享的只是其中…

python中sort返回值_Python函数你真的都学会了吗?来看看这篇Python高阶函数!

二、高阶函数高级函数, 英文叫 Higher-order Function.那么什么是高阶函数呢?在说明什么是高阶函数之前, 我们需要对函数再做进一步的理解!2.1 函数的本质函数的本质是什么?函数和函数名到底是一种什么关系?在python中,一切皆对象&#xff0…

基于数据库的事务消息解决分布式事务方案

转载请注明出处:http://www.cnblogs.com/lizo/p/8516502.html 概述 当单库已不能支撑当前业务的时候,我们往往都考虑进行分库(横向拆分或者纵向拆分)。但分库有个无法回避的问题,就是事务问题。网上有很多分布式事务解…

中service层的作用_浅析Java中dto、dao、service、controller的四层结构

目前我所在的项目组采用的是SpringBoot框架,前端使用BootStrapjQuery。SpringBoot是BS开发框架之一,不用单独开启tomcat服务器,目前比较流行,一般开发大型项目时会将所有的功能细分为许多小模块,每个模块都有dto、dao、…

【iOS开发必收藏】详解iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程!...

Himi 原创, 欢迎转载,转载请在明显处注明! 谢谢。 原文地址:http://blog.csdn.net/xiaominghimi/article/details/6937097 终于在11月公司的游戏即将上线了,那么对于iOS游戏来说当今都是内置道具收费属于主流&#xf…

不越狱换壁纸_终于来了!iOS 14.3 正式版,可自动定时换壁纸

嘿嘿,我没有猜错吧!iOS 14.3 正式版会在12月15日凌晨时段发布,在前几天我就有提到,这一天会发布,主要是与新品 AirPods Max 发售时间与iOS 14.3正式版发布时间一致。其次这次发布iOS 14.3正式版更新内容与 iOS 14.3 RC…

Hibernate【inverse和cascade属性】知识要点

Inverse属性 Inverse属性:表示控制权是否转移.. true:控制权已转移【当前一方没有控制权】false:控制权没有转移【当前一方有控制权】Inverse属性,是在维护关联关系的时候起作用的。只能在“一”的一方中使用该属性!Inverse属性的…

中list如何清空_如何根据索引删除 list 中的元素

这个问题很简单, 首先想到的就是a list(range(10)) del a[2]这个就可以很方便的删除掉 a 中的第 3 个元素.如果我想删除多个元素怎么办, 比如我想删除第 3, 4, 5, 6 个元素? 这个也很好办:a list(range(10)) del a[2:6]那么我要删除的元素的索引不连续呢? 比如我要删除第 3…

查询时拼接两列数据_如何用VBA代码查询两列数据差异?

爱就一个字,我只说一次……北京市第三交通委提醒您:代码千万条,注释第一条,命名不规范,修订两行泪……咳,给大家拜晚年了,再提前祝大家元宵快乐……我们今天和大家分享的内容是如何用VBA代码查询…

setTimeout详解

https://www.cnblogs.com/wzndkj/p/7069331.html 一、setTimeout基础 setTimeout(func|code,delay);第一个参数表示将要推迟的函数名或者一段代码,第二个参数表示推迟执行的毫秒数eg: console.log(1); setTimeout(console.log(2),1000); console.log(3);answer: 1 3…

Kappa电商负责人顾皓澜:电商业务一直保持盈利

Kappa电商负责人顾皓澜(TechWeb配图) 【TechWeb报道】10月初,闹得沸沸扬扬的淘宝商城事件吸引了无数互联网用户的目光,处于漩涡中心的淘宝商城当仁不让地挤入话题排行榜,商城上大小卖家的命运也牵动了众多消费者的心。…

Citrix VDI-in-a-Box 第二篇:架构篇

前言:为什么Citrix会收购Kaviza,就是因为其VDI-in-a-Box产品架构比较简单。 本文重点描述其架构和安装要求。 如果你想了解一个东西,首先必须了解其架构。 vdiManager是管理整个架构的工具,所有的虚拟机都运行在Hypervisor上。整 …

看完此文再不懂区块链算我输,用Python从零开始创建区块链

如果你还没有听说过 3 点钟区块链群,说明你还不是链圈的人;如果你还没有加入 3 点钟区块链群,说明你还不是链圈的大佬;如果你还没有被 3 点钟区块链群刷屏,说明你还体会不到什么是“币圈一天,人间一年”。 …

重新加一个window_Activity、View、Window关系,进程间通信,责任链模式,Https,数据存储...

码仔,今天就给大家带来了《每日一道面试题》的第九期:01理解Activity View window的关系 Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。 Activ…

排他网关(ExclusiveGateWay)

网关(ExclusiveGateWay) 作者:邓家海2018年3月11日 00:13:25 情景:某一家公司最近在给一个单位做一个财务审批的OA。具体需求是这样的:当部门申请的金额小于一万块的时候,财务部可以直接决策。当部分申请的金额大于一万…

离线安装宝塔lnmp_宝塔LNMP环境 Nginx安装EduSoho教程说明

[toc]宝塔下使用LNMP Nginx安装EduSoho创建站点 宝塔后台 > 网站 > 添加站点 > 输入信息 > 提交填写信息创建完成设置运行目录 宝塔后台 > 网站 > 管理运行目录选择web目录后保存 网站目录 > 运行目录 > 保存修改配置文件 配置文件 > 修改参数 >…

NHibernate初学者指南(10):一级和二级缓存

一级缓存 为了获得更好的性能,NHibernate智能地缓存数据。NHibernate有不同的缓存机制起作用,最重要的就是一级缓存。每个session对象维持一个一级缓存,session对象创建时缓存创建,session对象释放时缓存销毁。 缓存只不过是一个哈…

Freemarker模板引擎

模板引擎的实质就是将页面结构提前写好,然后将数据渲染到模板上生成一个静态页面,这样一来,下次就可以 直接访问静态文件,不用进行额外的获取数据的操作(例如:访问数据库),这样大大提…

postgresql主从备份_基于windows平台的postgresql主从数据库流备份配置

基于windows平台的postgresql主从数据库流备份配置因工作需要,需要搞pg数据库的主从备份,领导给了个方向使用流备份,于是开始朝着这个方向进发。鸣谢大佬A_ccelerator的博客一、配置主从库1.环境准备对于 pg 的主从库配置,建议是使…

msvcrt.lib和LIBCD.lib链接冲突

今天在移植一个开源代码到windows的VC6工程,编译时出现了这些奇怪的LINK错误。 msvcrt.lib(MSVCRT.dll) : error LNK2005: _toupper already defined in LIBCD.lib(toupper.obj)msvcrt.lib(MSVCRT.dll) : error LNK2005: _tolower already defined in LIBCD.lib(to…