本文主要介绍Golang中map基本用法和并发场景下的用法。
1.map的基本用法
map作为一种kv形式的数据结构,在各种编程语言中都会被高频使用到。那Golang中的ap该如何操作呢,话不多说,直接上代码。
1.1 声明和初始化
//声明空map,使用前切记初始化,否则会panicvar m map[int]int//使用make函数初始化mapm=make(map[int]int)//初始化时指定map大小m=make(map[int]int,100)//推荐,声明加初始化mm:=make(map[int]int)
1.2 增删改查和遍历
//增m[1]=1//改m[1]=2//查if val,ok:=m[1];ok{fmt.Println(val)}//删delete(m, 1)//遍历m[1]=11m[2]=22for k,v:=range m{fmt.Printf("key is %v,val is %v\n",k,v)}
2.map在并发场景下的使用
众所周知,Golang中内建的map并不是协程安全的,并发场景下的ap实现至少有4种方案。
2.1 互斥锁map
在读和写 map时加互斥锁,这个方案是协程安全的,但是效率肯定比较低。
2.2 读写锁map
给map加读写锁,借助读写锁本身的特性(多读可以同时进行;多写和读写不能同时进行)来提升效率。示例代码如下
type RWLockMap struct{sync.RWMutexm map[string]string}func NewRWLockMap(cap int) *RWLockMap{return &RWLockMap{m:make(map[string]string,cap),}}func (m *RWLockMap)Get(key string) (string , bool){m.RLock()defer m.RUnlock()v,ok:=m.m[key]return v,ok}func (m *RWLockMap)Set(key ,v string){m.Lock()defer m.Unlock()m.m[key]=v}func (m *RWLockMap)Delete(key string) {m.Lock()defer m.Unlock()delete(m.m, key)}
2.3 官方的syn.Map
sync.Map 的基本思路则是空间换时间,用两个数据结构(只读的 read 和可写的 dirty)将读写操作分开,来减少锁对性能的影响。基本用法如下,
//增m.Store("name","lyn")m.Store("gender","male")//改m.Store("name","victor" )//查v,ok:=m.Load("name")fmt.Printf("Load: v, ok = %v, %v\n", v, ok)//删m.Delete("name")//遍历f:=func(key,value interface{}) bool{fmt.Printf("range: k,v = %v, %v\n",key,value)return true}m.Range(f)
官方推荐的使用场景比较苛刻,要么是一写多读,要么是各个协程操作的 key 集合没有交集(或交集很少)。
2.4 分段锁
分段锁Map实现的思路是,将整个map分片,对每一片单独添加读写锁。通过减小锁的粒度来提高效率,目前已经有开源的实现,项目地址https://github.com/orcaman/concurrent-map/blob/master/concurrent_map.go
其核心部分源码如下,整个ConcurrentMap会有32个分片。当对整个大Map进行操作时,会先用key来计算哈希值,找到相对应目标分片,然后再进行操作,流程非常清晰。
var SHARD_COUNT = 32// A "thread" safe map of type string:Anything.// To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards.type ConcurrentMap []*ConcurrentMapShared// A "thread" safe string to anything map.type ConcurrentMapShared struct {items map[string]interface{}sync.RWMutex // Read Write mutex, guards access to internal map.}// Creates a new concurrent map.func New() ConcurrentMap {m := make(ConcurrentMap, SHARD_COUNT)for i := 0; i < SHARD_COUNT; i++ {m[i] = &ConcurrentMapShared{items: make(map[string]interface{})}}return m}// GetShard returns shard under given keyfunc (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared {return m[uint(fnv32(key))%uint(SHARD_COUNT)]}
整个源码不到400行,是宝贵的学习资料,非常推荐阅读。
3.小结
本文介绍了Golang内建map的基本用法和一些并发实现。其中sync.Map比较适用读多写少的场景;读写锁实现和分段锁比较通用,分段锁是读写锁实现的加强版,效率会是最高的。
参考阅读
https://juejin.cn/post/6844903895227957262
https://cloud.tencent.com/developer/article/1539049
https://zhuanlan.zhihu.com/p/356739568