转载本文请联系脑筋急转弯公众号。大家好,我是炸鱼。在之前的文章中,我们讨论了Go语言中map和slice的非线程安全性,并在此基础上推导出了目前业界使用最多并发支持的两种map模式。它们是:nativemap+mutex或read-writelockmutex。标准库sync.Map(Go1.9及更高版本)。有选择的时候,总是有选择的困难。这两者如何取舍,哪一个性能更好?有朋友说标准库sync.Map性能很好,不要用。我听谁的……今天建宇就带大家揭开Gosync.map的秘密。我们先来了解一下什么场景,如何使用各种类型的围棋地图,谁的性能最好!然后分析每个映射的性能结果,解剖sync.map的源代码以了解原因。让我们一起愉快地开启熏鱼之路吧。sync.Map优点Go官方文档中对Map类型的一些建议有明确的说明:多个goroutine并发使用是安全的,不需要额外的锁定或协调控制。大多数代码应该使用本机映射而不是单独的锁定或协调控件,以获得更好的类型安全性和可维护性。同时,Map类型还针对以下场景进行了性能优化:当给定键的条目仅写入一次但读取多次时。比如在一个只会增长的缓存中,就会有这样一个业务场景。当多个goroutine读取、写入和覆盖不相关的键集合的条目时。在这两种情况下,相对于Gomap带有单独的Mutex或RWMutex,使用Map类型可以大大减少锁争用。性能测试听了官方文档介绍了一堆好处,就不说缺点了,说的性能优化的优点是否真实可信。我们一起来验证一下。首先我们定义基本的数据结构://表示一个互斥锁typeFooMapstruct{sync.Mutexdatamap[int]int}//表示一个读写锁typeBarRwMapstruct{sync.RWMutexdatamap[int]int}varfooMap*FooMapvarbarRwMap*BarRwMapvarsyncMap*sync。Map//初始化基本数据结构funcinit(){fooMap=&FooMap{data:make(map[int]int,100)}barRwMap=&BarRwMap{data:make(map[int]int,100)}syncMap=&sync.Map{}}在配套的方法上,常见的增删改查,我们都写了相应的方法。后续压测(只展示部分代码):)deferbarRwMap.RUnlock()ifv,ok:=barRwMap.data[k];!ok{return-1}else{returnv}}funcbuiltinRwMapDelete(kint){barRwMap.Lock()deferbarRwMap.Unlock()if_,ok:=barRwMap.data[k];!ok{return}else{delete(barRwMap.data,k)}}其余类型和方法基本类似,考虑到重复空格的问题,这里不再赘述。压测方法的基本代码如下:.Unix()))forpb.Next(){k:=r.Intn(100000000)builtinRwMapDelete(k)}})}这块主要是增删改查的代码和压力测试的准备方法。压测代码直接被大白大佬的go19-examples/benchmark-for-map项目复用。也可以使用Go官方提供的map_bench_test.go。有兴趣的朋友可以拉下来运行试试。压测结果1)写入:方法名含义压测结果BenchmarkBuiltinMapStoreParalell-4map+mutex写元素237.1ns/opBenchmarkSyncMapStoreParalell-4sync.map写元素509.3ns/opBenchmarkBuiltinRwMapStoreParalell-4map+rwmutex写元素207.8以ns计/op写元素,最慢的是sync.map类型,其次是原生映射+互斥锁(Mutex),最快的是原生映射+读写锁(RwMutex)。整体排序(从慢到快)为:SyncMapStore
