导航

  • Golang sync.Map 详解
    • 简单的介绍一下 Golang Map
      • Map 使用
    • sync.Map
      • sync.Map 是什么
      • sync.Map 使用
      • sync.Map 剖析
      • sync.map 整体结构
    • 参考

Golang sync.Map 详解

原生的 Go Map 在并发读写场景下经常会遇到 panic 的情况。造成的原因是 map 是非线性安全的,并发读写过程中 map 的数据会被写乱。

而一般情况下,解决并发读写 map 的思路是加锁,或者把一个 map 切分成若干个小 map,对 key 进行哈希。
在业界中使用最多并发指出的模式分别是:

  • 原生 map + 互斥锁 或者 读写锁
  • 标准库 sync.Map (Go 1.9 及之后)

简单的介绍一下 Golang Map

Go 语言中 map 是一个 key(索引)和 value(值)形式的无序集合,也可以称为关联数组或字典;Golang中的 map 所有的 key 都是不同,通过给定的 key 可以在常数时间复杂度内检索、更新或删除对应的 value。

Golang map 的 key 是任何可以使用 == 进行比较的数据类型,如 int、string、bool 等,value 可以是任意类型。

Map是一种无序的数据结构,因此 map 的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。

Map 使用

// 声明
var mapName map[keyType]valueType
// 创建
mapName = make(map[keyType]valueType, len) // 需要声明
mapName := make(map[keyType]valueType, len) // 声明+创建
// 循环遍历
for key, value := range mapName{}
// 删除
delete(mapName, "Key")

sync.Map

sync.Map 是什么

Go 语言原生 map 是非线性安全的,对 map 进行并发读写操作时,需要加锁。在 Go 1.9 引入 sync.map,一种并发安全的 map。

sync.map 是线性安全的,读取、插入、删除都保持常数级的时间复杂度。
sync.map 的零值是有效的,并且零值是一个空的 map。在第一次使用之后,不允许被拷贝。

接着看一下在 Go 官方文档中明确指出 sync.Map 类型的一些建议:

Map is like a Go map[interface{}]interface{} but is safe for concurrent use by multiple goroutines without additional locking or coordination. Loads, stores, and deletes run in amortized constant time.

The Map type is specialized. Most code should use a plain Go map instead, with separate locking or coordination, for better type safety and to make it easier to maintain other invariants along with the map content.

The Map type is optimized for two common use cases: (1) when the entry for a given key is only ever written once but read many times, as in caches that only grow, or (2) when multiple goroutines read, write, and overwrite entries for disjoint sets of keys. In these two cases, use of a Map may significantly reduce lock contention compared to a Go map paired with a separate Mutex or RWMutex.

The zero Map is empty and ready for use. A Map must not be copied after first use.

  • 多个 goroutine 的并发使用是安全的,不需要额外的锁或协程(coordination 直译协调,但笔者认为翻译为协程更符合)
  • 大多数代码应该使用原生的 map,而不是单独的锁或协程控制,以获得更好的类型安全性和维护性

同时 Map类型,还针对以下场景进行了性能优化:

  1. 当一个 key 只被写入一次但被多次读取时,例如在只会增长的缓存中,就存在这种业务场景
  2. 当多个 goroutines 读取、写入和覆盖不相干的 key 时

这两种情况与 Go map 搭配单独的 Mutex 或 RWMutex 相比较,使用 sync.Map 类型可以大大减少锁的争夺。

sync.Map 使用

官方的 API 接口
示例代码

package mainimport ("fmt""sync"
)func main()  {var m sync.Map// 1. 写入m.Store("test", 18)m.Store("mo", 20)// 2. 读取age, _ := m.Load("test")fmt.Println(age.(int))// 3. 遍历m.Range(func(key, value interface{}) bool {name := key.(string)age := value.(int)fmt.Println(name, age)return true})// 4. 删除m.Delete("test")age, ok := m.Load("test")fmt.Println(age, ok)// 5. 读取或写入m.LoadOrStore("mo", 100)age, _ = m.Load("mo")fmt.Println(age)
}

sync.map 适用于读多写少的场景。对于写多的场景,会导致 read map 缓存失效,需要加锁,导致冲突变多;而且由于未命中 read map 次数过多,导致 dirty map 提升为 read map,这是一个 O(N) 的操作,会进一步降低性能。

sync.Map 剖析

sync.Map 类型的底层数据结构如下:

type Map struct {mu Mutexread atomic.Value // readOnlydirty map[interface{}]*entrymisses int
}// Map.read 属性实际存储的是 readOnly。
type readOnly struct {m       map[interface{}]*entryamended bool
}
  • mu: 互斥锁,保护 read 和 dirty
  • read: 只读数据,指出并发读取 (atomic.Value 类型) 。如果需要更新 read,需要加锁保护数据安全。
    • read 实际存储的是 readOnly 结构体,内部是一个原生 map,amended 属性用于标记 read 和 dirty 的数据是否一致
  • dirty: 读写数据,非线性安全的原生 map。包含新写入的 key,并且包含 read 中所有未被删除的 key。
  • misses: 统计有多少次读取 read 没有被命中。每次 read 读取失败后,misses 的计数加 1。

在 read 和 dirty 中,都涉及到的结构体:

type entry struct {p unsafe.Pointer // *interface{}
}

其中包含一个 p 指针,用于指向用户存储的元素(key)所指向的 value 值。看来,read 和 dirty 各自维护一套 key,key 指向的都是同一个 value。也就是说,只要修改了这个 entry,对 read 和 dirty 都是可见的。这个指针的状态有三种:

p 的三种状态

当 p == nil 时,说明这个键值对已被删除,并且 m.dirty == nil,或 m.dirty[k] 指向该 entry。

当 p == expunged 时,说明这条键值对已被删除,并且 m.dirty != nil,且 m.dirty 中没有这个 key。

其他情况,p 指向一个正常的值,表示实际 interface{} 的地址,并且被记录在 m.read.m[key] 中。如果这时 m.dirty 不为 nil,那么它也被记录在 m.dirty[key] 中。两者实际上指向的是同一个值。

当删除 key 时,并不实际删除。一个 entry 可以通过原子地(CAS 操作)设置 p 为 nil 被删除。如果之后创建 m.dirty,nil 又会被原子地设置为 expunged,且不会拷贝到 dirty 中。

如果 p 不为 expunged,和 entry 相关联的这个 value 可以被原子地更新;如果 p == expunged,那么仅当它初次被设置到 m.dirty 之后,才可以被更新。

sync.map 整体结构

sync.Map 的两个 map,当从 sync.Map 类型中读取数据时,其会先查看 read 中是否包含所需的元素:

  • 若有,则通过 atomic 原子操作读取数据并返回。
  • 若无,则会判断 read.readOnly 中的 amended 属性,他会告诉程序 dirty 是否包含 read.readOnly.m 中没有的数据;因此若存在,也就是 amended 为 true,将会进一步到 dirty 中查找数据。

sync.Map 的读操作性能如此之高的原因,就在于存在 read 这一巧妙的设计,其作为一个缓存层,提供了快路径(fast path)的查找。

同时其结合 amended 属性,配套解决了每次读取都涉及锁的问题,实现了读这一个使用场景的高性能。

参考

  1. 年度最佳【golang】sync.Map详解
  2. sync.Map

sync.Map详解相关推荐

  1. php小程序地图处理,微信小程序 地图map详解及简单实例

    微信小程序 地图map 微信小程序map 地图属性名类型默认值说明longitudeNumber中心经度 latitudeNumber中心纬度 scaleNumber1缩放级别 markersArra ...

  2. java map详解

    java map详解 Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键对象和一个值对象.其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类 ...

  3. .map文件 源映射(Source Map)详解

    一.什么是源映射 为了提高性能,很多站点都会先压缩 JavaScript 代码然后上线, 但如果代码运行时出现错误,浏览器只会显示在已压缩的代码中的位置,很难确定真正的源码错误位置. 这时源映射就登场 ...

  4. java中list和map详解

    java中list和map详解 一.概叙 List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口, List下有ArrayList,Vector,LinkedL ...

  5. java中set和ge什么么意思,java的Collection和Map详解

    java的Collection和Map详解 线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构.这些类均在java.util包中.本文 ...

  6. 目标检测指标mAP详解

    前言 相信刚刚接触目标检测的小伙伴也是有点疑惑吧,目标检测的知识点和模型属实有点多,想要工作找CV的话,目标检测是必须掌握的方向了.我记得在找实习的时候,面试官就问到了我目标检测的指标是什么,答:mA ...

  7. jquery源码解析:each,makeArray,merge,grep,map详解

    jQuery的工具方法,其实就是静态方法,源码里面就是通过extend方法,把这些工具方法添加给jQuery构造函数的. jQuery.extend({ ...... each: function( ...

  8. [转] java的 Collection 和 Map 详解

    原文转自: http://www.diybl.com/course/3_program/java/javajs/2007917/71621.html 前言        线性表,链表,哈希表是常用的数 ...

  9. python lambda ,map详解

    lambda 匿名函数 1 # 普通定义函数 2 def func1(x,y): 3 return x+y 4 # 执行函数 5 print(func(1,2)) 6 # 如果此函数只调用一次,或者功 ...

最新文章

  1. Linux 磁盘挂载
  2. 2022年人工智能全球最具影响力学者榜单AI 2000
  3. 成为技术领导者——解决问题的有机方法
  4. index.html example demonstration
  5. 线程池什么时候调用shutdown方法_ThreadPoolExecutor.shutdown()?
  6. 全面详细的jQuery常见开发技巧手册
  7. 有限覆盖定理证明区间套_圆内整点问题的开普勒猜想证明,关于圆内整点问题误差项的估值E(r)=1-x,x=sin(nx)...
  8. javafx 图标_JavaFX技巧32:需要图标吗? 使用Ikonli!
  9. ApacheCN 学习资源汇总 2019.3 1
  10. JAVA如何选中一行上移,怎么把表格一行整体上移
  11. 【POJ 3276】【开关问题】Face The Right Way【暑期 No.4】
  12. python urllib.parse_Python3的urllib.parse常用函数小结(urlencode,quote,quote_plus,unquote,unquote_plus等)...
  13. 【建议收藏】50 道硬核的 Python 面试题
  14. Cecil学C#界面编程——配置环境和入门
  15. 蓝牙音箱延迟测试软件,“Latency Test”详细操作流程,一款测试TWS耳机延迟的软件...
  16. 缓慢的 HTTP 的拒绝服务攻击
  17. (第39册)《微信小程序游戏开发快速入门到实战》夏敏捷著
  18. 在线观看视频--使用代码倍速播放
  19. 如何用C语言来求次幂(快速幂算法)
  20. 前端,移动端开发框架

热门文章

  1. 图的存储结构(邻接矩阵和邻接表)
  2. 【习题35】交互程序三 + 汉化版
  3. 利用预测分析改进欠款催收策略,控制欺诈风险和信贷风险
  4. 怀揣感恩之心,学会感恩
  5. 菜鸟教程python100题:递归函数
  6. Linux内核读文件处理过程浅析
  7. 为什么手机网速太慢_为什么手机网速太慢
  8. 微波雷达人体感应器,即时存在感知方案,智能家居人体感应交互
  9. 九型人格:一、The Perfectionist 完美主义者 - 我若不完美,就没有人会爱我。
  10. 青云QingCloud 在不同场景化中的云计算应用