本文主要介绍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