美文网首页
给hash表分片:降低锁粒度,提高锁性能

给hash表分片:降低锁粒度,提高锁性能

作者: 浩成聊技术 | 来源:发表于2022-12-15 23:47 被阅读0次

锁就像漏斗,将并发处理的多个线程变成串行化的模式,我们可以构建一个支持成千上万并发的系统,但是如果锁处理的不好会严重影响系统的性能,就像拥有多条车道的高速公路变成了单行道。

举个例子,假如我们使用gomap来实现一个简单的缓存,由于map不是并发安全,所以我们还要借助sync包的锁来保证并发安全,于是我们很容易写出下面这样的代码:

package simple_cache

import (
    "sync"
)

type Cache struct {
    items map[string][]byte
    lock  *sync.RWMutex
}

func New() *Cache {
    return &Cache{
        items: make(map[string][]byte, 2000),
        lock:  new(sync.RWMutex),
    }
}

func (c *Cache) Get(key string) []byte {
    // 取数据只要加读锁
    c.lock.RLock()
    defer c.lock.RUnlock()
    return c.items[key]
}

func (c *Cache) Set(key string, data []byte) {
    c.lock.Lock()
    defer c.lock.Unlock()
    c.items[key] = data
}

这段代码考虑到了锁其实已经算是不错了,但是每次调用set()方法去设置缓存值的时候不仅将并发读写变成了串行化的模式,就连get()方法也会被阻塞住。在实际生产中使用这段代码作为缓存的时候,map中会缓存大量数据,set()调用可能会很频繁,而且在set()内还需要判断缓存的容量是否足够,这些都会使锁的时间变长。

然后我们不得不考虑如何优化一下锁的性能。上面代码的问题是每次set()都锁住了整个map,于是我们就想到能不能只锁住一部分,这样就能降低锁对性能的消耗。我们可以把原先这个大的缓存分成若干个小的分片,每个分片就是原先的一个Cache,然后再将这些分片放入一个大的map中,根据缓存key值通过hash计算后的值找到对应的分片。对上面代码改造如下:

package simple_cache

import (
    "crypto/sha1"
    "fmt"
    "sync"
)

type Cache map[string]*ShardCache

type ShardCache struct {
    items map[string][]byte
    lock  *sync.RWMutex
}

func NewCache() *Cache {
    cache := make(Cache, 256)
    for i := 0; i < 256; i++ {
        cache[fmt.Sprintf("%02x", i)] = &ShardCache{
            items: make(map[string][]byte, 2000),
            lock:  new(sync.RWMutex),
        }
    }
    return &cache
}

func (c Cache) getShard(key string) *ShardCache {
    hasher := sha1.New()
    hasher.Write([]byte(key))
        // 转16进制后取前两位
    shardKey := fmt.Sprintf("%x", hasher.Sum(nil))[0:2]
    return c[shardKey]
}

func (c Cache) Get(key string) []byte {
    // 取数据只要加读锁
    shard := c.getShard(key)
    shard.lock.RLock()
    defer shard.lock.RUnlock()
    return shard.items[key]
}

func (c Cache) Set(key string, data []byte) {
    shard := c.getShard(key)
    shard.lock.Lock()
    shard.lock.Unlock()
    shard.items[key] = data
}

这里我们一共给缓存设置了256(16^2)个分片,对于任意的一个缓存key值经过hash后通过fmt.Sprintf("%x", hasher.Sum(nil))[0:2]转16进制后取前两位后都能在缓存中找到对应的分片

其实像java里面的ConcurrencyHashmap已经是这样做的了,我们通过hash计算数据存储的所在的分片,虽然消耗一点点计算资源但是解决了锁粒度大导致的锁性能问题,这是很值得的。

总结

  • 通过对hash表分片,大锁拆小锁,降低锁粒度,提高高并发情况下的锁性能

相关文章

  • 给hash表分片:降低锁粒度,提高锁性能

    锁就像漏斗,将并发处理的多个线程变成串行化的模式,我们可以构建一个支持成千上万并发的系统,但是如果锁处理的不好会严...

  • 世界上最简单的无锁哈希表

    无锁哈希表(Lock-Free Hash Table )可以提高多线程下的性能表现,但是因为实现一个无锁哈希表本身...

  • mysql锁

    mysql锁 性能:乐观锁,悲观锁 操作类型:读锁,写锁,都属于悲观锁 操作粒度:行锁,表锁 乐观锁:一种思想,通...

  • mysql锁记录

    mysql锁 性能:乐观锁,悲观锁 操作类型:读锁,写锁,都属于悲观锁 操作粒度:行锁,表锁 乐观锁:一种思想,通...

  • MySQL--锁

    MySQL 锁 锁的类型 行锁粒度最小的锁,存在死锁。 页锁粒度在行锁和表锁之间的锁。 表锁粒度较大的锁,不存在死...

  • 锁升级

    什么是锁升级?(Lock Escalation) 指将当前锁的粒度降低。比如: 把行锁升级为页锁。 将页锁升级为表...

  • 第四章 锁的优化

    1. 提高锁性能的几点建议 1.1 减小锁持有时间 1.2 减小锁粒度 例如ConcurrentHashMap,将...

  • Mysql锁有哪些,如何理解 --- 2021-09-14

    按锁的粒度分: 行锁,锁某行数据,锁粒度最小,并发度高 表锁,锁整张表,锁粒度最大,并发低 间隙锁,锁的是一个区间...

  • MySQL 锁机制——必知必会

    行锁、表锁对比 开销、加锁速度、死锁、粒度、并发性能 表锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概...

  • mysql 锁机制

    开销、加锁速度、死锁、粒度、并发性能l 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲...

网友评论

      本文标题:给hash表分片:降低锁粒度,提高锁性能

      本文链接:https://www.haomeiwen.com/subject/khalqdtx.html