go 并发安全map 分段锁实现

一. 简言

1.1 go中的map不是并发安全的

1.2 go1.9版本之前,可以使用map+mutex的方式实现并发安全,但是每次操作,无论读取都要加锁,性能不太好

1.3 go 1.9之后,新增了sync.Map,是并发安全的,效率也很高,具体的源码分析可见笔者的另外一篇博客

        https://blog.csdn.net/yzf279533105/article/details/98108367

1.4 类似java的ConcurrentHashMap的实现,可以对key进行分段,一个段内使用一个锁,这样操作不同的key时,避免锁的阻塞开        销,大大提高效率,这篇博客我们实现的就是go语言的分段锁

二. 效率验证(与map+锁,sync.map的性能对比)

参见笔者的另外一篇博客

concurrentmap,sync.map,map+锁的效率比对

三. 代码实现

// 总的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
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值