sync.Map 是 Go 中协程安全缓存的官方推荐方案,适合读多写少、键值不固定场景;它通过分片和双 map 结构实现无锁读与局部写锁,性能优于 map+mutex,但不支持遍历删除、无 len 方法且键须可比较。

在 Go 中实现协程安全的缓存,sync.Map 是最直接、轻量且官方推荐的方式之一,特别适合读多写少、键值不固定、或无法预估容量的场景。它内部已做并发控制,无需额外加锁,但需注意其设计约束和使用习惯。
普通 map 不是并发安全的,配合 sync.RWMutex 虽可行,但在高并发读场景下,读锁仍会竞争;而 sync.Map 采用分片+延迟初始化+只读/读写双 map 结构,读操作几乎无锁,写操作局部加锁,性能更优,尤其适合缓存类高频读、低频写的模式。
不过要注意:sync.Map 不支持遍历中删除、不保证迭代顺序、不提供 len() 方法(需自行计数)、键类型必须可比较(如 string、int,不能是 slice 或 map)。
sync.Map 本身不支持 TTL(Time-To-Live),需自行管理过期逻辑。常见做法是将 value 封装为结构体,内嵌时间戳,在 Get 时判断是否过期(惰性过期),或配合后台 goroutine 定期清理(主动过期)。以下为轻量惰性方案:
立即学习“go语言免费学习笔记(深入)”;
type CacheItem struct {
Value interface{}
ExpiresAt time.Time
}
type SafeCache struct {
data sync.Map
}
func (c *SafeCache) Set(key string, value interface{}, ttl time.Duration) {
expires := time.Now().Add(ttl)
c.data.Store(key, CacheItem{Value: value, ExpiresAt: expires})
}
func (c *SafeCache) Get(key string) (interface{}, bool) {
if item, ok := c.data.Load(key); ok {
if cacheItem, ok := item.(CacheItem); ok {
if time.Now().Before(cacheItem.ExpiresAt) {
return cacheItem.Value, true
}
c.data.Delete(key) // 过期即删
}
}
return nil, false
}
func (c *SafeCache) Delete(key string) {
c.data.Delete(key)
}类似 Java 的 ConcurrentHashMap.computeIfAbsent,Go 中常需要“若不存在则生成并缓存”。可封装一个 GetOrSet 方法,配合 once-per-key 控制并发生成:
func (c *SafeCache) GetOrSet(key string, fn func() interface{}, ttl time.Duration) interface{} {
// 先尝试读
if val, ok := c.Get(key); ok {
return val
}
// 未命中:用 sync.Once per key 搞定(需额外 map 存 once)
// 简单起见,此处用 double-check + Store,适用于低冲突场景
// 生产建议用 singleflight.Group 替代(更健壮)
if item, loaded := c.data.LoadOrStore(key, &lazyItem{
fn: fn,
ttl: ttl,
done: make(chan struct{}),
}); loaded {
lz := item.(*lazyItem)
<-lz.done
return lz.value
} else {
lz := item.(*lazyItem)
lz.value = fn()
lz.expires = time.Now().Add(ttl)
close(lz.done)
c.data.Store(key, CacheItem{Value: lz.value, ExpiresAt: lz.expires})
return lz.value
}
}
// 注:实际项目中更推荐用 golang.org/x/sync/singleflight 避免“缓存击穿”当你的缓存有明确容量上限、需 LRU/LFU 驱逐策略、或要求强一致性与精确 size 控制时,sync.Map 就不太合适了。此时可考虑:
sync.RWMutex + map + container/list:完全可控,适合有定制驱逐逻辑的场景记住:sync.Map 是工具,不是银弹。选型前先明确 QPS、key 规模、过期精度、内存敏感度等指标。
以上就是如何在Golang中实现协程安全缓存_结合sync.Map使用示例的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号