map 的两种目前在业界使用的最多的并发支持的模式分别是:

 map + mutexsync.Map
 sync.Map 
Go sync.mapGo map 
map  sync.map 

一起愉快地开始吸鱼之路。

1、sync.Map 优势

在 Go 官方文档中明确指出 Map 类型的一些建议:

Go 并发读写 sync.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

Go 并发读写 sync.map 详细

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