设计思路
- 分段锁策略:将大的map分割成多个小的map,每个小map对应一个独立的锁。这样在高并发读多写少场景下,不同的写操作可以并行处理不同的小map,减少锁争用。
- 读优化:利用Go语言的特性,读操作可以无锁进行,通过使用原子操作来更新版本号,在读操作时检查版本号以判断数据是否被修改。如果数据被修改,则重新读取。
关键代码实现
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// 定义分段数量
const numSegments = 16
// 分段map结构体
type Segment struct {
mu sync.RWMutex
items map[interface{}]interface{}
}
// 自定义线程安全map结构体
type ThreadSafeMap struct {
segments [numSegments]Segment
version uint64
}
// 创建新的线程安全map
func NewThreadSafeMap() *ThreadSafeMap {
tsm := &ThreadSafeMap{}
for i := range tsm.segments {
tsm.segments[i].items = make(map[interface{}]interface{})
}
return tsm
}
// 获取分段索引
func (tsm *ThreadSafeMap) getSegmentIndex(key interface{}) int {
// 简单的哈希取模
h := hash(key)
return int(h % numSegments)
}
// 哈希函数
func hash(key interface{}) uint32 {
// 简单实现,实际可使用更好的哈希算法
var h uint32
switch k := key.(type) {
case int:
h = uint32(k)
case string:
for _, c := range k {
h = 31*h + uint32(c)
}
}
return h
}
// 设置键值对
func (tsm *ThreadSafeMap) Set(key, value interface{}) {
index := tsm.getSegmentIndex(key)
tsm.segments[index].mu.Lock()
defer tsm.segments[index].mu.Unlock()
tsm.segments[index].items[key] = value
atomic.AddUint64(&tsm.version, 1)
}
// 获取键对应的值
func (tsm *ThreadSafeMap) Get(key interface{}) (interface{}, bool) {
var version uint64
for {
index := tsm.getSegmentIndex(key)
tsm.segments[index].mu.RLock()
value, ok := tsm.segments[index].items[key]
tsm.segments[index].mu.RUnlock()
version = atomic.LoadUint64(&tsm.version)
// 检查版本号,若被修改则重新读取
if ok {
newVersion := atomic.LoadUint64(&tsm.version)
if newVersion == version {
return value, true
}
} else {
return nil, false
}
}
}
性能优化点
- 减少锁粒度:通过分段锁策略,不同的写操作可以并行处理不同的小map,降低锁争用,提高并发性能。
- 读操作优化:读操作无锁,仅在数据可能被修改时检查版本号重新读取,减少读操作的开销。
- 哈希函数优化:可采用更高效的哈希函数,如FNV哈希算法,减少哈希冲突,进一步提升性能。