当前位置: 首页 > 后端技术 > PHP

Go并发读写sync.map的强大

时间:2023-03-29 19:06:57 PHP

大家好,我是建宇。在之前的《为什么 Go map 和 slice 是非线程安全的?》文章中,我们讨论了Go语言中map和slice的非线程安全性。基于此,我们推导出目前业界支持最多的两种地图模式。它们是:nativemap+mutex或read-writelockmutex。标准库sync.Map(Go1.9及更高版本)。有了选择,总是有选择的困难。这两者如何选择,哪一个性能更好?有朋友说标准库sync.Map性能很好,不要用。我听谁的……今天建宇就带大家揭开Gosync.map的秘密。我们先来了解一下什么场景,如何使用各种类型的围棋地图,谁的性能最好!然后,根据每个map的性能分析结果,解剖sync.map的源代码,了解WHY。让我们一起愉快地开启熏鱼之路吧。sync.Map的优势在Go官方文档中明确提出了Map类型的一些建议:多个goroutine并发使用是安全的,不需要额外的锁定或协调控制。大多数代码应该使用本机映射而不是单独的锁定或协调控件,以获得更好的类型安全性和可维护性。同时,Map类型还针对以下场景进行了性能优化:当给定键的条目只写入一次但读取多次时。比如在一个只会增长的缓存中,就会有这样一个业务场景。当多个goroutine读取、写入和覆盖不相关的键集合的条目时。在这两种情况下,相对于Gomap带有单独的Mutex或RWMutex,使用Map类型可以大大减少锁争用。性能测试听了官方文档介绍了一堆好处,就不说缺点了,说的性能优化的优点是否真实可信。我们一起来验证一下。首先,我们定义基本数据结构://表示一个互斥锁类型FooMapstruct{sync.Mutexdatamap[int]int}//表示一个读写锁类型BarRwMapstruct{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{}}在配套的方法上,对于常见的增删改查动作,我们都写了相应的方法。后续压测(只展示部分代码):{barRwMap.RLock()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)}}其余类型和方法基本类似,考虑到重复空格的问题,这里就不展示了。压测方法的基本代码如下:funcBenchmarkBuiltinRwMapDeleteParalell(b*testing.B){b.RunParallel(func(pb*testing.PB){r:=rand.New(rand.NewSource(time.Now().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.8ns/op正在写入对于输入元素,最慢的是sync.map类型,其次是原生映射+互斥锁(Mutex),最快的是原生映射+读写锁(RwMutex)。总体排序(从慢到快)是:SyncMapStore