本文主要介绍Golang中map基本用法和并发场景下的用法。
1.map的基本用法
map作为一种kv形式的数据结构,在各种编程语言中都会被高频使用到。那Golang中的ap该如何操作呢,话不多说,直接上代码。
1.1 声明和初始化
//声明空map,使用前切记初始化,否则会panic
var m map[int]int
//使用make函数初始化map
m=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]=11
m[2]=22
for 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.RWMutex
m 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 key
func (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