Golang分段锁

简介: Golang分段锁

介绍


因为golang的原生map是非并发安全的,所以为了保证map的并发安全,最简单的方式就是给map加锁。直接对一个map加锁,当访问map的请求越来越多,都竞争这一把锁使得并发访问变慢。


分段锁是一种锁的设计,并不是具体的一种锁,分段锁顾名思义就是将锁分段,将锁的粒度变小,将存储的对象分散到各个分片(shard)中,每个分片由一把锁控制,我们将 key 分散到固定数量的 shard 中避免 rehash 操作。shard 是有锁保护的 map, 当 shard 进行 rehash 时会阻塞shard内的读写,但不会对其他 shard 造成影响。这样使得当需要对在A分片上的数据进行读写时不会影响B分片的读写。


虽然有sync.Map存在,但是通过压力测试对比,分段锁的性能更好,下面给出分段锁代码

代码

package main
import (
  "math"
  "sync"
  "sync/atomic"
)
type ConcurrentMap struct {
  shard      []*MapShard
  count      int32
  shardCount uint32
}
type MapShard struct {
  m     map[string]interface{}
  mutex sync.RWMutex
}
//该参数转成二进制,每个位都赋为1
func computeCapacity(param int) int {
  if param <= 16 {
    return 16
  }
  n := param - 1
  n |= n >> 1
  n |= n >> 2
  n |= n >> 4
  n |= n >> 8
  n |= n >> 16
  if n < 0 {
    return math.MaxInt32
  }
  return n + 1
}
// MakeConcurrentMap 返回一个分段锁的实例
func MakeConcurrentMap(shardCount int) *ConcurrentMap {
  shardCount = computeCapacity(shardCount)
  shard := make([]*MapShard, shardCount)
  for idx := range shard {
    shard[idx] = &MapShard{
      m:     make(map[string]interface{}),
      mutex: sync.RWMutex{},
    }
  }
  return &ConcurrentMap{
    shard:      shard,
    count:      0,
    shardCount: uint32(shardCount),
  }
}
const prime32 = uint32(16777619)
func fnv32(key string) uint32 {
  hash := uint32(2166136261)
  for i := 0; i < len(key); i++ {
    hash *= prime32
    hash ^= uint32(key[i])
  }
  return hash
}
func (dict *ConcurrentMap) getShardMap(key string) *MapShard {
  hashCode := fnv32(key)
  idx := (dict.shardCount - 1) & hashCode
  return dict.shard[idx]
}
func (dict *ConcurrentMap) Get(key string) (val interface{}, exists bool) {
  shard := dict.getShardMap(key)
  shard.mutex.RLock()
  defer shard.mutex.RUnlock()
  val, exists = shard.m[key]
  return
}
func (dict *ConcurrentMap) Len() int {
  return int(atomic.LoadInt32(&dict.count))
}
// Set 插入
func (dict *ConcurrentMap) Set(key string, val interface{}) int {
  shard := dict.getShardMap(key)
  shard.mutex.Lock()
  defer shard.mutex.Unlock()
  if _, ok := shard.m[key]; ok {
    shard.m[key] = val
    return 0
  }
  shard.m[key] = val
  atomic.AddInt32(&dict.count, 1)
  return 1
}
// Remove 删除一个key
func (dict *ConcurrentMap) Remove(key string) int {
  shard := dict.getShardMap(key)
  shard.mutex.Lock()
  defer shard.mutex.Unlock()
  if _, ok := shard.m[key]; ok {
    delete(shard.m, key)
    atomic.AddInt32(&dict.count, -1)
    return 1
  }
  return 0
}
目录
相关文章
|
6月前
|
安全 Go
Golang深入浅出之-互斥锁(sync.Mutex)与读写锁(sync.RWMutex)
【4月更文挑战第23天】Go语言并发编程中,`sync.Mutex`和`sync.RWMutex`是保证线程安全的关键。互斥锁确保单个goroutine访问资源,而读写锁允许多个读者并发访问。常见问题包括忘记解锁、重复解锁以及混淆锁类型。使用`defer`可确保解锁,读写锁不支持直接升级或降级,需释放后再获取。根据读写模式选择合适锁以避免性能下降和竞态条件。理解并正确使用锁是编写并发安全程序的基础。
130 3
|
2月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
88 4
|
3月前
|
SQL 安全 Java
golang为什么不支持可重入锁?
本文对比分析了Java与Go语言中锁机制的不同。在Java中,无论是`synchronized`关键字还是`ReentrantLock`都支持可重入特性,通过维护一个计数器来跟踪锁的嵌套级别,确保同一线程可以多次获取同一把锁而不会造成死锁。然而,Go语言的`sync.Mutex`并不支持这一特性,其设计理念认为可重入锁往往指向代码设计问题,鼓励开发者重构代码以避免此类需求。文章进一步解释了这种设计理念背后的原因,并提供了替代方案示例。总体而言,Go语言试图从设计层面避免潜在的代码问题,尽管这可能会增加一定的开发复杂性。
golang为什么不支持可重入锁?
|
6月前
|
Go
Golang 中的互斥锁是什么?
# go # programming # beginners # architecture
|
6月前
|
存储 Go
浅谈Golang互斥锁sync.Mutex
浅谈Golang互斥锁sync.Mutex
55 0
|
6月前
|
存储 编译器 Go
Golang底层原理剖析之互斥锁sync.Mutex
Golang底层原理剖析之互斥锁sync.Mutex
109 0
|
Go
Golang 语言标准库 sync 包的 RWMutex 读写互斥锁怎么使用?
Golang 语言标准库 sync 包的 RWMutex 读写互斥锁怎么使用?
72 0
|
安全 Go
golang中的互斥锁和管道
golang中的互斥锁和管道
|
存储 Go 调度
golang 锁原理剖析,你值得收藏
golang 锁原理剖析,你值得收藏