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采用了两级缓存实现,一级缓存做无锁并发,二级缓存做有锁并发,如:

对上图说明两点:
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,数据又要反向同步一次)。

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

  1. 面试官系统精讲Java源码及大厂真题 - 10 Map源码会问哪些面试题

    10 Map源码会问哪些面试题 更新时间:2019-09-10 10:34:08 人的一生可能燃烧也可能腐朽,我不能腐朽,我愿意燃烧起来! --奥斯特洛夫斯基 引导语 Map 在面试中,占据了很大一部 ...

  2. go sync.map 源码分析

    一 简言 1.1 文件所在目录:go/src/sync/map.go 1.2 本篇博客的go版本:go1.10.3 二 原理说明 空间换时间.通过冗余的两个数据结构(read.dirty),实现加锁对 ...

  3. 【Golang 源码】sync.Map 源码详解

    sync.Map 不安全的 map go 中原生的 map 不是并发安全的,多个 goroutine 并发地去操作一个 map 会抛出一个 panic package main import &quo ...

  4. go语言如何调用java接口_Go语言实现的Java Stream API

    学习Go语言时实现的集合操作工具库,类似于Java 8 中新增的Stream API.由于Go语言不支持泛型,所以基于反射实现.只用于学习目的,不要用于生产(PS:当然也不会有人用). 集合操作包括生 ...

  5. c语言api函数写病毒,C语言病毒代码,及写病毒简单介绍

    编制病毒的语言 最常见的编制病毒的语言有汇编语言.VB.C 语言等,我们可以来看一看一个有名的病毒论坛上认为学写病 毒要掌握的基础: 1).Win32编程,进程,线程,内存,等等. 2).32位汇编, ...

  6. sync.Map 源码学习

    golang 线程安全的 Map 作者水平有限,而并发博大精深. 如文章中有任何错误, 希望读者不吝指出.谢谢! 章节目录 Map 基本类型定义 Store Load Delete Range Map ...

  7. Linux平台上SQLite数据库教程(二)——C语言API介绍

    http://blog.csdn.net/u011192270/article/details/48086961 前言:本文将介绍几个基本的SQLite3数据库的C语言API接口,主要用到两个文件:s ...

  8. 易语言API激活窗口SetWindowPos源码

    我们用到两个API,一个是FindWindowA寻找窗口句柄,一个是SetWindowPos激活窗口 一.API .版本 2.DLL命令 寻找窗口, 整数型, "user32", ...

  9. e语言通用进销存源码_Go 语言设计哲学之五:代码风格的唯一标准

    一. gofmt Go 语言设计的目标之一就是解决大型软件系统的大规模开发的问题,解决大型团队的开发问题,Go 核心团队给它起了一个名字叫:规模化(scale). gofmt 是伴随着 Go 语言诞生 ...

最新文章

  1. 长得类似铁甲小宝的机器人_铁甲小宝:小时候只顾看机器人忽略重点,长大后再看:是我太天真...
  2. java safevarargs_@SafeVarargs注解的使用
  3. hashCode之一--两个对象值相同,有相同的hash code
  4. 死锁终结者:顺序锁和轮询锁!
  5. 深入JVM系列(二)之GC机制、收集器与GC调优
  6. windows - Hook技术介绍
  7. 正态分布概率函数积分推导伽马函数性质
  8. 代码大全 服装尺寸图html,国际标准服装尺码对照表大全-实用衣服尺寸对照表...
  9. 什么是域名备案?为什么要进行备案?备案后你将会获得下列益处
  10. 贝叶斯决策类条件概率密度估计:最大似然和贝叶斯参数估计
  11. 关于浏览器自动转https
  12. 2015中国企业500强名单
  13. Java黑皮书课后题第4章:4.4(几何:六边形面积)六边形面积可以通过下面公式计算(s是边长) 编写程序,提示用户输入六边形的边长,然后显示它的面积
  14. 记录来到结算页面的客户
  15. DNA: 人类的终极U盘
  16. 我的世界服务器无线刷物品,《我的世界》1.12无限刷物品方法图文教学
  17. Python 进阶指南(编程轻松进阶):一、处理错误和寻求帮助
  18. 世界500强企业名称中英对照(续)
  19. 操作系统的内存管理你知道吗
  20. windows cmd命令使用ls

热门文章

  1. 实现物联网项目,你需要提前知道的6件事情
  2. Ubuntu链接ubuntu服务器
  3. JQuery常用的代码片段
  4. WinCE切换GPRS
  5. 项目中遇到的ORA error 及解决办法 ---ora-07445
  6. 《WCF技术内幕》翻译25:第2部分_第5章_消息:创建一个消息(下)之MessageFault
  7. 我看windows mobile数据同步方案
  8. 安装DirectX SDK时出现Error Code:s1023 的解决方案
  9. 方差和协方差的数据意义
  10. linux不登录用户就关机,Linux无法被远程登录;用户的关机, 重启,注销,新增用户,删除用户...