分段锁ConcurrentMap的实现请参考笔者的另外一篇博客:

​​go 分段锁ConcurrentMap的实现源码​​

效率测试结论:

1. go自带的map不是多协程安全的

2. 分段锁ConcurrentMap是多协程安全的,且效率最高;sync.map效率次之,传统的map+读写锁效率最低

3. ConcurrentMap中锁的个数越多,效率越高,因为争夺同一把锁的概率降低了

测试流程:

1. 产生1万个key-value对,加入到各个测试map中

2. 开始计时

3. 开启两个协程,一个写协程随机赋值10万次,一个读协程随机访问10万次

4. 结束计时,打印出耗时,内存操作等信息

测试结果如下图(红框处是每次测试的耗时):

go 分段锁ConcurrentMap,map+读写锁,sync.map的效率测试_i++

源码如下:

测试的源码main_test.go

package main

import (
"math/rand"
"strconv"
"sync"
"testing"
)

// 10万次的赋值,10万次的读取
var times int = 100000

// 测试ConcurrentMap
func BenchmarkTestConcurrentMap(b *testing.B) {
for k := 0; k < b.N; k++ {
b.StopTimer()
// 产生10000个不重复的键值对(string -> int)
testKV := map[string]int{}
for i := 0; i < 10000; i++ {
testKV[strconv.Itoa(i)] = i
}

// 新建一个ConcurrentMap
pMap := NewConcurrentMap()

// set到map中
for k, v := range testKV {
pMap.Set(k, v)
}

// 开始计时
b.StartTimer()

wg := sync.WaitGroup{}
wg.Add(2)

// 赋值
go func() {
// 对随机key,赋值times次
for i := 0; i < times; i++ {
index := rand.Intn(times)
pMap.Set(strconv.Itoa(index), index+1)
}
wg.Done()
}()

// 读取
go func() {
// 对随机key,读取times次
for i := 0; i < times; i++ {
index := rand.Intn(times)
pMap.Get(strconv.Itoa(index))
}
wg.Done()
}()

// 等待两个协程处理完毕
wg.Wait()
}
}

// 测试map加锁
func BenchmarkTestMap(b *testing.B) {
for k := 0; k < b.N; k++ {
b.StopTimer()
// 产生10000个不重复的键值对(string -> int)
testKV := map[string]int{}
for i := 0; i < 10000; i++ {
testKV[strconv.Itoa(i)] = i
}

// 新建一个MutexMap
pMap := NewMutexMap()

// set到map中
for k, v := range testKV {
pMap.Set(k, v)
}

// 开始计时
b.StartTimer()

wg := sync.WaitGroup{}
wg.Add(2)
// 赋值
go func() {
// 对随机key,赋值100万次
for i := 0; i < times; i++ {
index := rand.Intn(times)
pMap.Set(strconv.Itoa(index), index+1)
}
wg.Done()
}()

// 读取
go func() {
// 对随机key,读取100万次
for i := 0; i < times; i++ {
index := rand.Intn(times)
pMap.Get(strconv.Itoa(index))
}
wg.Done()
}()

// 等待两个协程处理完毕
wg.Wait()
}
}

// 测试sync.map
func BenchmarkTestSyncMap(b *testing.B) {
for k := 0; k < b.N; k++ {
b.StopTimer()
// 产生10000个不重复的键值对(string -> int)
testKV := map[string]int{}
for i := 0; i < 10000; i++ {
testKV[strconv.Itoa(i)] = i
}

// 新建一个sync.Map
pMap := &sync.Map{}

// set到map中
for k, v := range testKV {
pMap.Store(k, v)
}

// 开始计时
b.StartTimer()

wg := sync.WaitGroup{}
wg.Add(2)

// 赋值
go func() {
// 对随机key,赋值10万次
for i := 0; i < times; i++ {
index := rand.Intn(times)
pMap.Store(strconv.Itoa(index), index+1)
}
wg.Done()
}()

// 读取
go func() {
// 对随机key,读取10万次
for i := 0; i < times; i++ {
index := rand.Intn(times)
pMap.Load(strconv.Itoa(index))
}
wg.Done()
}()

// 等待两个协程处理完毕
wg.Wait()
}
}

分段锁map的实现源码concurrentmap.go

package main

import (
"sync"
)

// 总的map
type ConcurrentMap []*ConcurrentMapShared

// 默认分片数
const SHARE_COUNT int = 64

// 单个map分片
type ConcurrentMapShared struct {
items map[string]interface{} // 本分片内的map
mu sync.RWMutex // 本分片的专用锁
}

// 新建一个map
func NewConcurrentMap() *ConcurrentMap {
m := make(ConcurrentMap, SHARE_COUNT)
for i := 0; i < SHARE_COUNT; i++ {
m[i] = &ConcurrentMapShared{
items: map[string]interface{}{},
}
}
return &m
}

// GetSharedMap 获取key对应的map分片
func (m ConcurrentMap) GetSharedMap(key string) *ConcurrentMapShared {
return m[uint(fnv32(key))%uint(SHARE_COUNT)]
}

// hash函数
func fnv32(key string) uint32 {
hash := uint32(2166136261)
prime32 := uint32(16777619)
for i := 0; i < len(key); i++ {
hash *= prime32
hash ^= uint32(key[i])
}
return hash
}

// Set 设置key,value
func (m ConcurrentMap) Set(key string, value interface{}) {
sharedMap := m.GetSharedMap(key) // 找到对应的分片map
sharedMap.mu.Lock() // 加锁(全锁定)
sharedMap.items[key] = value // 赋值
sharedMap.mu.Unlock() // 解锁
}

// Get 获取key对应的value
func (m ConcurrentMap) Get(key string) (value interface{}, ok bool) {
sharedMap := m.GetSharedMap(key) // 找到对应的分片map
sharedMap.mu.RLock() // 加锁(读锁定)
value, ok = sharedMap.items[key] // 取值
sharedMap.mu.RUnlock() // 解锁
return value, ok
}

// Count 统计key个数
func (m ConcurrentMap) Count() int {
count := 0
for i := 0; i < SHARE_COUNT; i++ {
m[i].mu.RLock() // 加锁(读锁定)
count += len(m[i].items)
m[i].mu.RUnlock() // 解锁
}
return count
}

// Keys1 所有的key方法1(方法:遍历每个分片map,读取key;缺点:量大时,阻塞时间较长)
func (m ConcurrentMap) Keys1() []string {
count := m.Count()
keys := make([]string, count)

// 遍历所有的分片map
for i := 0; i < SHARE_COUNT; i++ {
m[i].mu.RLock() // 加锁(读锁定)
oneMapKeys := make([]string, len(m[i].items))
for k := range m[i].items {
oneMapKeys = append(oneMapKeys, k)
}
m[i].mu.RUnlock() // 解锁

// 汇总到keys
keys = append(keys, oneMapKeys...)
}

return keys
}

// Keys2 所有的key方法2(方法:开多个协程分别对分片map做统计再汇总 优点:量大时,阻塞时间较短)
func (m ConcurrentMap) Keys2() []string {
count := m.Count()
keys := make([]string, count)

ch := make(chan string, count) // 通道,遍历时
// 单独起一个协程
go func() {
wg := sync.WaitGroup{}
wg.Add(SHARE_COUNT)

for i := 0; i < SHARE_COUNT; i++ {
// 每个分片map,单独起一个协程进行统计
go func(ms *ConcurrentMapShared) {
defer wg.Done()

ms.mu.RLock() // 加锁(读锁定)
for k := range ms.items {
ch <- k // 压入通道
}
ms.mu.RUnlock() // 解锁
}(m[i])
}

// 等待所有协程执行完毕
wg.Wait()
close(ch) // 一定要关闭通道,因为不关闭的话,后面的range不会结束!!!
}()

// 遍历通道,压入所有的key
for k := range ch {
keys = append(keys, k)
}
return keys
}

map+读写锁的实现源码mutexmap.go

package main

import (
"sync"
)

// 对外暴露的map
type MutexMap struct {
items map[string]interface{} // 为了和上面的ConcurrentMap做比较,都采用string->interface的方式
mu *sync.RWMutex // 读写锁
}

// 新建一个map
func NewMutexMap() *MutexMap {
return &MutexMap{
items: map[string]interface{}{},
mu: new(sync.RWMutex),
}
}

// Set 设置key,value
func (m MutexMap) Set(key string, value interface{}) {
m.mu.Lock() // 加锁(全锁定)
m.items[key] = value // 赋值
m.mu.Unlock() // 解锁
}

// Get 获取key对应的value
func (m MutexMap) Get(key string) (value interface{}, ok bool) {
m.mu.RLock() // 加锁(读锁定)
value, ok = m.items[key] // 取值
m.mu.RUnlock() // 解锁
return value, ok
}

// Count 统计key个数
func (m MutexMap) Count() int {
m.mu.RLock() // 加锁(读锁定)
count := len(m.items)
m.mu.RUnlock() // 解锁
return count
}

// Keys 所有的key
func (m MutexMap) Keys() []string {
m.mu.RLock() // 加锁(读锁定)
keys := make([]string, len(m.items))
for k := range m.items {
keys = append(keys, k)
}
m.mu.RUnlock() // 解锁

return keys
}