Sharded Concurrent Map
Reduce lock contention with a fixed set of independently locked shards, safe for concurrent writes from many goroutines.
Code
package main
import (
"fmt"
"hash/fnv"
"sync"
)
type shard[K comparable, V any] struct {
mu sync.RWMutex
data map[K]V
}
type ShardedMap[K comparable, V any] struct {
shards []*shard[K, V]
}
func NewShardedMap[K comparable, V any](count int) *ShardedMap[K, V] {
if count <= 0 {
panic("shard count must be positive")
}
m := &ShardedMap[K, V]{shards: make([]*shard[K, V], count)}
for i := range m.shards {
m.shards[i] = &shard[K, V]{data: make(map[K]V)}
}
return m
}
func (m *ShardedMap[K, V]) shardFor(key K) *shard[K, V] {
hasher := fnv.New32a()
fmt.Fprintf(hasher, "%v", key)
return m.shards[int(hasher.Sum32())%len(m.shards)]
}
func (m *ShardedMap[K, V]) Set(key K, value V) {
s := m.shardFor(key)
s.mu.Lock()
s.data[key] = value
s.mu.Unlock()
}
func (m *ShardedMap[K, V]) Get(key K) (V, bool) {
s := m.shardFor(key)
s.mu.RLock()
value, ok := s.data[key]
s.mu.RUnlock()
return value, ok
}
func (m *ShardedMap[K, V]) Delete(key K) {
s := m.shardFor(key)
s.mu.Lock()
delete(s.data, key)
s.mu.Unlock()
}
func (m *ShardedMap[K, V]) Len() int {
total := 0
for _, s := range m.shards {
s.mu.RLock()
total += len(s.data)
s.mu.RUnlock()
}
return total
}
func main() {
cache := NewShardedMap[string, int](8)
var wg sync.WaitGroup
for worker := 0; worker < 16; worker++ {
wg.Add(1)
go func(worker int) {
defer wg.Done()
for i := 0; i < 100; i++ {
cache.Set(fmt.Sprintf("key-%d-%d", worker, i), i)
}
}(worker)
}
wg.Wait()
fmt.Println("entries:", cache.Len())
value, ok := cache.Get("key-3-42")
fmt.Println("lookup:", value, ok)
cache.Delete("key-3-42")
_, ok = cache.Get("key-3-42")
fmt.Println("after delete:", ok)
}Report a problem with this exampleOpens a pre-filled issue on GitHub.