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

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

go 分段锁ConcurrentMap的实现源码

效率测试结论:

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

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

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

测试流程:

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

2. 开始计时

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

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

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

源码如下:

测试的源码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
}

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值