- 背景
go内置的map不是线程安全的,如何
无锁实现线程安全的map呢?
- 非线程安全的map
package main
import (
"fmt"
"strconv"
"sync"
)
var d = make(map[string]int)
var w sync.WaitGroup
func get(key string) int {
return d[key]
}
func set(key string,value int) {
d[key]=value
}
func main(){
for i :=0;i<300;i++{
w.Add(1)
go func(n int) {
key := strconv.Itoa(n)
set(key,n)
fmt.Printf("k=:%v,v:=%v\n",key,get(key))
w.Done()
}(i)
}
w.Wait()
}
当并发数量大时,程序会报错
- 读写锁实现
package main
import (
"fmt"
"strconv"
"sync"
)
var d2 = make(map[string]int)
var w2 sync.WaitGroup
var lo sync.RWMutex
func getValue(key string) int {
lo.RLock()
defer lo.RUnlock()
return d2[key]
}
func setValue(key string,value int) {
lo.Lock()
defer lo.Unlock()
d2[key]=value
}
func main(){
for i :=0;i<3000;i++{
w2.Add(1)
go func(n int) {
key := strconv.Itoa(n)
setValue(key,n)
fmt.Printf("k=:%v,v:=%v\n",key,getValue(key))
w2.Done()
}(i)
}
w2.Wait()
}
- 字典分片
# 相对直接针对整个字典加读写锁,分片锁 可以
# 缩小控制的区域 提高性能
package main
import "sync"
var SHARD_COUNT = 32
// 分成SHARD_COUNT个分片的map
type ConcurrentMap []*ConcurrentMapShared
// 通过RWMutex保护的线程安全的分片,包含一个map
type ConcurrentMapShared struct {
items map[string]interface{}
sync.RWMutex // Read Write mutex, guards access to internal map.
}
// 创建并发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
}
// 根据key计算分片索引
func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared {
return m[uint(fnv32(key))%uint(SHARD_COUNT)]
}
#读取时
func (m ConcurrentMap) Set(key string, value interface{}) {
// 根据key计算出对应的分片
shard := m.GetShard(key)
shard.Lock() //对这个分片加锁,执行业务操作
shard.items[key] = value
shard.Unlock()
}
func (m ConcurrentMap) Get(key string) (interface{}, bool) {
// 根据key计算出对应的分片
shard := m.GetShard(key)
shard.RLock()
// 从这个分片读取key的值
val, ok := shard.items[key]
shard.RUnlock()
return val, ok
}
- sync.map
# 使用sync.map实现
package main
import (
"fmt"
"strconv"
"sync"
)
var syncMap sync.Map
func main(){
wait := sync.WaitGroup{}
for i:=0;i<3000;i++{
wait.Add(1)
go func(n int) {
key :=strconv.Itoa(n)
syncMap.Store(key,n)
value,_ := syncMap.Load(key)
fmt.Printf("k=:%v,v:=%v\n", key, value)
wait.Done()
}(i)
}
wait.Wait()
}
- 并发map的使用要求
类型 | 场景 |
Sync.map | 1.只会增长的缓存系统中,一个 key 只写入一次而被读很多次 2.多个 goroutine 为不相交的键集读、写和重写键值对 使用场景比较局限,不常用 |
读写锁实现的map | 并发要求不高 使用方便 |
分片机制实现的map | 常用,并发要求高 |
可以根据实际业务场景测试三种不同的并发性能再做选择
- 进一步深度学习
sync.map的实现原理
- 好文推荐