35 | 并发安全字典sync.Map (下)
该思维导图由 AI 生成,仅供参考
知识扩展
问题 1:怎样保证并发安全字典中的键和值的类型正确性?(方案二)
- 深入了解
- 翻译
- 解释
- 总结
`sync.Map`是Go语言中的一种并发安全字典类型,通过内部的原生字典和巧妙的设计,尽可能避免使用锁,提高了并发安全字典的性能。本文讨论了如何保证并发安全字典中的键和值的类型正确性,提出了两种方案:一种是在编码时确定键和值的类型,另一种是在程序运行时通过反射操作进行类型检查。这两种方案各有利弊,需要根据实际情况进行选择。此外,文章还提出了思考题,鼓励读者思考其他可能的方案。通过对`sync.Map`内部结构的分析,读者可以了解并发安全字典的适用场景和不同操作对性能的影响程度。整体而言,本文深入浅出地介绍了并发安全字典的设计原理和性能优化,为读者提供了有价值的技术参考。
《Go 语言核心 36 讲》,新⼈⾸单¥59
全部留言(28)
- 最新
- 精选
- sky郝大 go方面能推荐下比较成熟的微服务框架吗
作者回复: 在我发布的Github优秀Go语言项目的思维导图里有。
2018-11-01212 - 下雨天老师好,关于:sync.Map在存储键值对的时候,只要只读字典中已存有这个键,并且该键值对未被标记为“已删除”,就会把新值存到里面并直接返回,这种情况下也不需要用到锁。这句话,只读map里面的值可以被替换的话,为什么不需要加锁?不会 有读写冲突吗?
作者回复: 【第一个层面】 因为这里面的只读字典 read 是通过 sync/atomic.Value 存储的,又正因为 read 是只读的,不存在增删键值对的情况,所以从 read 整体的层面可以安全地操作其中的某个键值对。 【第一个层面:更具体的细节】 在这种保护下,只要这里的键值对在数量上没有增减,就不会出现新数据丢失或者弄脏数据的情况。反例的话,可以看我最近写的这篇文章:https://mp.weixin.qq.com/s/ru161EtyQMrQVtWji0CqzQ 【第二个层面】 由于其中的每一个键值对(entry 结构)都可以保证自身的并发性( entry 内部只会存指针,因此用原子操作就可以保证并发读写的安全性),所以从 read 中单个键值对的层面也就有了并发安全性方面的保证。 【结论】 综合以上两种措施,这里才无需使用锁来保护。
2020-10-207 - 疯琴这个设计很巧妙,在自己的开发中可以借鉴这种思想。有个问题请问老师: “脏字典中的键值对集合总是完全的”,而“read 和 dirty 互换之后 dirty 会置空”,那么重建的意思是不是这样的:在下一次访问 read 的时候,将 read 中的键值对全部复制到 dirty 中?
作者回复: read 和 dirty 互换是分两步走的。Load 的时候如果发现“不得不去 dirty 中查找”的情况已经有很多了,就会把 dirty 作为新的 read,然后把 dirty 置为 nil。之后,在 Store 的时候,如果发现健是新的,而且是对于新 read 的第一个新健(此时 dirty 必定为 nil),那么就重新初始化 dirty,然后把新 read 中的有效键一个一个地存入 dirty。 另外你可以尝试阅读一下 sync.Map 的源码,写得还是挺清晰的。可以配合着这里的文章去看。
2020-01-0525 - 渺小de尘埃当一个结构体里的字段是sync.map类型的,怎么json序列化呢?
作者回复: 既然要序列化了就用不着同步了吧,用个普通map倒腾一下呗。或者你再包装一下,自定义序列化过程。
2018-11-013 - 金时// The read field itself is always safe to load, but must only be stored with mu held. 老师,源代码里对read变量注释时说read 在store时,需要加锁,没懂这是为什么?
作者回复: 你应该知道,atomic.Value 类型的值(以下简称“value”吧)只能保证(完整地)存/取操作的原子性。比如,你在里面存一个 map,它只能保证存这个 map 或取这个 map 的时候是原子操作,但你如果要存、取、改、删这个 map 里的键值对,那 value 就管不到了(这时就会是非原子的操作)。 另外,这里还有一个问题,如果有多个 goroutine 同时向同一个 value 里存值,那么,里面到底存成了哪一个 goroutine 提供的值就不好说了。如果没有并发保护,多个并发写非常容易造成问题。 最后,你看源码肯定也知道,read 里存的是私有类型 readOnly 的实例,这个类型本身并不是并发安全的。所以无论对它做什么操作,都需要并发安全保护。 这也跟“并发多写”的问题有关。 所以,无论是替换 read 里的 readOnly 值(如果是单 goroutine 写就不用,可惜这里不可能做出这种保证),还是修改该值的内部,都需要有锁的保护。 你看,原子操作虽然能保证单值存取的原子性,但还是太简单了,在很多场景下是不足以完全保证并发安全的。不过,作为“可升级的并发安全保障”中的第一级防护还是挺好用的,就像 sync.Map 里做的那样。 sync.Map 的 Store 方法和 Load 方法里都是有一个升级保障的过程的,“升级”的标志就是用了 m.mu 。
2021-07-102 - mkii老师,看到源码中Store的时候有个疑惑。如果read中存在此key对应的vlaue,则tryStore替换read的value。这里如果在dirty给read并将dirty置为nil的时候不会丢失新数据吗?
作者回复: 不会啊,这里操作(包括存取和转移)的都是指针啊。
2021-03-0432 - 夜来寒雨晓来风关于文中提到的“键值对应该被删除,但却仍然存在于只读字典”,什么时候会出现这种情形呢?对于sync.Map的删除机制看的不是很明白,希望能解答一下,谢谢~
作者回复: 虽然在只读字典里,但是你肯定是获取不到的。这样做是为了操作效率,是一种“用空间换时间”的做法。之后内部做整理的时候,这些都会被一并删除的。
2021-01-072 - Geek_a8be59看了一下源码有地方不理解,有劳解答一下 第一:Store、Load等方法都会执行两次m.read.Load().(readOnly),去判断两次 这样做的目的是什么?
作者回复: 主要是怕第一次的操作没有体现真实情况,所以在锁的保护下再来一次。这就相当于快路径和慢路径。
2020-07-2122 - 一介农夫1.18后可以用泛型实现了
作者回复: 嗯嗯,你可以利用泛型改进一下这里的代码。试试看。
2023-03-06归属地:浙江1 - xl000老师,read、dirty交换和访问read这两个操作,难道不需要保护read变量吗
作者回复: 交换时会有互斥锁的保护,而 read 实际上存在了一个 sync/atomic.Value 里面。所以相关操作都是并发安全的。
2021-06-211