09 | map:如何实现线程安全的map类型?
晁岳攀
该思维导图由 AI 生成,仅供参考
你好,我是鸟窝。
哈希表(Hash Table)这个数据结构,我们已经非常熟悉了。它实现的就是 key-value 之间的映射关系,主要提供的方法包括 Add、Lookup、Delete 等。因为这种数据结构是一个基础的数据结构,每个 key 都会有一个唯一的索引值,通过索引可以很快地找到对应的值,所以使用哈希表进行数据的插入和读取都是很快的。Go 语言本身就内建了这样一个数据结构,也就是 map 数据类型。
今天呢,我们就先来学习 Go 语言内建的这个 map 类型,了解它的基本使用方法和使用陷阱,然后再学习如何实现线程安全的 map 类型,最后我还会给你介绍 Go 标准库中线程安全的 sync.Map 类型。学完了这节课,你可以学会几种可以并发访问的 map 类型。
map 的基本使用方法
Go 内建的 map 类型如下:
其中,key 类型的 K 必须是可比较的(comparable),也就是可以通过 == 和 != 操作符进行比较;value 的值和类型无所谓,可以是任意的类型,或者为 nil。
在 Go 语言中,bool、整数、浮点数、复数、字符串、指针、Channel、接口都是可比较的,包含可比较元素的 struct 和数组,这俩也是可比较的,而 slice、map、函数值都是不可比较的。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
Go语言内建map类型存在并发访问问题,本文介绍了其基本使用方法和常见错误,以及解决方案。作者详细介绍了如何利用读写锁实现线程安全的map类型,避免并发读写panic,并介绍了分片加锁的方法以提高并发性能。此外,还介绍了Go官方线程安全map的标准实现sync.Map及其优化点。总的来说,本文对于学习如何实现线程安全的map类型的读者具有很高的参考价值。文章还提到了sync.Map适合增长缓存系统,不适合频繁删除和新增元素,以及一些思考题供读者深入探讨。文章内容丰富,涵盖了map类型的并发访问问题及解决方案,适合对Go语言并发编程感兴趣的读者阅读。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 并发编程实战课》,新⼈⾸单¥59
《Go 并发编程实战课》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(23)
- 最新
- 精选
- 蜉蝣老师好,我看到 read 中 key 被删除会有两个状态:nil 和 expunged。我会有些不明白,要么都用 nil 或者都用 expunged,这样会不会更好一些?
作者回复: 第一你说的没错:nil和expunged都代表元素被删除了,只不过expunged比较特殊,如果被删除的元素是expunged,代表它只存在于readonly之中,不存在于dirty中。这样如果重新设置这个key的话,需要往dirty增加key
2020-11-1238 - tingting想问一下老师,以下这种情况会有data race吗? m:=make(map[string]int) Goroutine A: 不停地覆盖m指向新的map值 Goroutine B: 不停地读m里面的某个key
作者回复: 会。 遇到这样的问题不如写个测试验证一下
2022-03-24 - 叶小彬老师,我看了sync.Map 源码,有两点不是很懂 1、设计read和dirty的想法是什么 因为sync.Map里,read结构本身就是atomic.Value,增加和修改有Store方法,本身就可以防止幻读,脏读的问题,如果是为了delete的逻辑(我发现atomic.Value里是没有delete方法的),那完全可以写一个加锁逻辑的delete,个人感觉dirty的没什么用 2、源码里的逻辑是,当read 的miss次数大于等于dirty的长度的时候,就将dirty转成read,这个是什么设计想法
作者回复: 只读不需要加锁,快
2021-11-07 - 徐改还是不太明白为什么在创建dirty的时候,要将read中未删除的entry拷贝给dirty. sync.map一个优秀的地方是当我们访问read的时候不需要上锁,访问dirty的时候需要加锁。在Load()方法中,我们每次都是先访问read,如果read中没有的话才访问dirty。那么对于dirty来说,dirty中的数据可能read没有,或者read有。read中有的数据,dirty有;read中没有的数据,dirty可能会有。而我们的程序每次都是先访问read,如果read没有后续才会访问dirty,那这样的话创建dirty的时候,感觉可以不用将read中的entry一个一个拷贝到dirty中,因为我们访问是先访问read的。
作者回复: 因为dirty将来可能转为readonly
2021-10-27 - 校歌老师,发现有个地方不严谨,”map不可比较”。我写了个小程序,提示map只可以跟nil比较,而不是不能比较。(可能有点扣字眼了) ./main.go:11:12: invalid operation: resMap == resMap (map can only be compared to nil)
作者回复: 是的,准确的说可以和nil比较
2021-05-14 - 新味道新加的元素需要放入到 dirty 中,如果 dirty 为 nil,那么需要从 read 字段中复制出来一个 dirty 对象。 --- 为什么需要从 read 字段中复制出来一个 dirty 对象?
作者回复: 因为dirty中也要能查到这些值的
2020-11-26 - 约书亚应该着重说明一下为什么有expunged这种状态,这点比较迷惑。我能理解expunged的entry代表read中存在而dirty中不存在。但为什么在read向dirty复制时,需要将nil的entry变为expunged?
作者回复: nil和expunged都代表元素被删除了,只不过expunged比较特殊,如果被删除的元素是expunged,代表它只存在于readonly之中,不存在于dirty中。这样如果重新设置这个key的话,需要往dirty增加key
2020-11-133 - Panmax文章中写到「所以,这里我们就超前一把,我带你直接体验这个即将要发布的泛型方案。」 是我对泛型的理解有什么误会吗,下文中并没有看到使用泛型的地方������。
作者回复: 这句话应该删除,谢谢指出
2020-10-31 - Junes1. 双检查主要是针对高并发的场景: 第一次先用CAS快速尝试,失败后进行加锁,然后进行第二次CAS检查,再进行修改; 在高并发的情况下,存在多个goroutine在修改同一个Key,第一次CAS都失败了,在竞争锁;如果不进行第二次CAS检查就直接修改,这个Key就会被多次修改; 2. 真正删除key的操作是在数据从read往dirty迁移的过程中(往dirty写数据时,发现dirty没有数据,就会触发迁移),只迁移没有被标记为删除的KV2020-10-30532
- 我来也看到本文的标题,就让我想到之前看过的一篇文章: [踩了 Golang sync.Map 的一个坑](https://gocn.vip/topics/10860) 就是老师文章代码中的一行注释的由来: `这一行长坤在1.15中实现的时候忘记加上了,导致在特殊的场景下有些key总是没有被回收` 当时我是好好把系统的sync.Map代码看了一下. 虽然才短短384行代码,但还是花了不少功夫. 另外,推荐一个 欧长坤 未完工的开源电子书 [Go 语言原本](https://github.com/golang-design/under-the-hood).2020-10-30114
收起评论