功能说明
内置 map 不是并发安全的,包一层全局互斥锁虽然正确,但所有读写都会排队。分片映射把键空间按哈希分散到若干片,每片有独立的读写锁,不同片的操作可以真正并行。分片数量通常是二的幂或 CPU 核数的若干倍,示例使用八片。
这个结构适合进程内缓存、连接注册表、计数聚合等写多读也多的场景。每个分片内部仍然是普通 map,逻辑简单,便于审查。
关键代码
shardFor 用 FNV-1a 对键的字符串形式求哈希,再对分片数取模。Set 与 Delete 拿写锁,Get 拿读锁,Len 逐个分片读取长度并累加。main 启动十六个协程各写入一百条记录,用 WaitGroup 等待全部完成,然后验证总数、查询和删除。闭包把 worker 作为参数传入,避免循环变量捕获问题。
使用注意
Len 不是原子快照,遍历期间其他协程仍可能写入,得到的只是近似值。键的字符串化使用 fmt.Fprintf,存在分配开销,热点路径可以针对字符串键改用直接哈希。分片数量在创建后固定,不能动态扩容。没有提供范围遍历,需要遍历时应实现按片加读锁的 Range 回调。删除不存在的键不会报错。哈希碰撞只会让不同键落进同一片,不影响正确性,只影响并发度。
验证方式
十六个协程各写一百个不同键,最终 entries 应为 1600。查询 key-3-42 返回 42 和 true,删除后再次查询返回 false。可以在启动时加上竞态检测器运行,确认没有数据竞争报错;把分片数改为一,吞吐会明显下降,但结果保持一致。
代码示例
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)
}