Content #
虽然使用读写锁可以提供线程安全的 map,但是在大量并发读写的情况下,锁的 竞争会非常激烈。我在第 4 讲中提到过,锁是性能下降的万恶之源之一。
在并发编程中,我们的一条原则就是尽量减少锁的使用。一些单线程单进程的应 用(比如 Redis 等),基本上不需要使用锁去解决并发线程访问的问题,所以 可以取得很高的性能。但是对于 Go 开发的应用程序来说,并发是常用的一个特 性,在这种情况下,我们能做的就是,尽量减少锁的粒度和锁的持有时间。
你可以优化业务处理的代码,以此来减少锁的持有时间,比如将串行的操作变成 并行的子任务执行。不过,这就是另外的故事了,今天我们还是主要讲对同步原 语的优化,所以这里我重点讲如何减少锁的粒度。
减少锁的粒度常用的方法就是分片(Shard),将一把锁分成几把锁,每个锁控 制一个分片。Go 比较知名的分片并发 map 的实现是orcaman/concurrent-map。
它默认采用 32 个分片,GetShard 是一个关键的方法,能够根据 key 计算出分 片索引。
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))