map 的两种目前在业界使用的最多的并发支持的模式分别是:
map + mutexsync.Map
sync.Map
Go sync.mapGo map
map sync.map
一起愉快地开始吸鱼之路。
1、sync.Map 优势
在 Go 官方文档中明确指出 Map 类型的一些建议:
goroutine map
Map
goroutines
Go map Mutex RWMutex Map
2、性能测试
听官方文档介绍了一堆好处后,他并没有讲到缺点,所说的性能优化后的优势又是否真实可信。我们一起来验证一下。
首先我们定义基本的数据结构:
在配套方法上,常见的增删改查动作我们都编写了相应的方法。用于后续的压测(只展示部分代码):
其余的类型方法基本类似,考虑重复篇幅问题因此就不在此展示了。
压测方法基本代码如下:
go19-examples/benchmark-for-map
也可以使用 Go 官方提供的 map\_bench\_test.go,有兴趣的小伙伴可以自己拉下来运行试一下。
2.1 压测结果
1)写入
名 | 含义 | 压测结果 |
---|---|---|
BenchmarkBuiltinMapStoreParalell-4 | map+mutex 写入元素 | 237.1 ns/op |
BenchmarkSyncMapStoreParalell-4 | sync.map 写入元素 | 509.3 ns/op |
BenchmarkBuiltinRwMapStoreParalell-4 | map+rwmutex 写入元素 | 207.8 ns/op |
SyncMapStore < MapStore < RwMapStore。
2)查找
方法名 | 含义 | 压测结果 |
---|---|---|
BenchmarkBuiltinMapLookupParalell-4 | map+mutex 查找元素 | 166.7 ns/op |
BenchmarkBuiltinRwMapLookupParalell-4 | map+rwmutex 查找元素 | 60.49 ns/op |
BenchmarkSyncMapLookupParalell-4 | sync.map 查找元素 | 53.39 ns/op |
sync.map
MapLookup < RwMapLookup < SyncMapLookup。
3)删除
方法名 | 含义 | 压测结果 |
---|---|---|
BenchmarkBuiltinMapDeleteParalell-4 | map+mutex 删除元素 | 168.3 ns/op |
BenchmarkBuiltinRwMapDeleteParalell-4 | map+rwmutex 删除元素 | 188.5 ns/op |
BenchmarkSyncMapDeleteParalell-4 | sync.map 删除元素 | 41.54 ns/op |
map+ map+sync.map
RwMapDelete < MapDelete < SyncMapDelete
2.3 场景分析
sync.Map
- 在读和删场景上的性能是最佳的,领先一倍有多。
- 在写入场景上的性能非常差,落后原生 map+锁整整有一倍之多。
因此在实际的业务场景中。假设是读多写少的场景,会更建议使用 sync.Map 类型。
goroutine
3、sync.Map 剖析
清楚如何测试,测试的结果后。我们需要进一步深挖,知其所以然。
sync.Map
3.1 数据结构
sync.Map
muread dirtyreadatomic.Value read readOnly mapamended read dirty dirtymapdirty missesread read misses
read dirty
value
readdirtyentry
3.2 查找过程
Map mapreaddirty
sync.Map 的 2 个 map
当我们从 sync.Map 类型中读取数据时,其会先查看 read 中是否包含所需的元素:
atomic read.readOnly amended read.readOnly.m amended
sync.Map
amended
3.3 写入过程
sync.Map Store
源码如下:
Load m.read
若该元素不存在或已经被标记为删除状态,则继续走到下面流程:
dirty Lock
其分为以下三个处理分支:
expungeddirty nilexpungedentry read dirty read dirty
我们理一理,写入过程的整体流程就是:
readread
回到最初的话题,为什么他写入性能差那么多。究其原因:
readdirty read
sync.Map
若有大数据量的场景,则需要考虑 read 复制数据时的偶然性能抖动是否能够接受。
3.4 删除过程
sync.Map
源码如下:
read
delete expungedread
若不存在,也就是走到 dirty 流程中:
read dirty read dirty amended dirty
read delete dirty
需要注意,出现频率较高的 delete 方法:
entry.p nilexpunged
sync.Mapkey buffer
总结:
sync.Map
map
原文链接:https://segmentfault.com/a/1190000040729053