Go中实现并发安全缓存需用sync.RWMutex配map,读用RLock允许多个goroutine并发,写用Lock保证独占;基础结构含mu和data,Get/Set/Delete方法分别加读锁、写锁;高并发可分片优化锁粒度。

在 Go 中实现并发安全的缓存,核心是避免多个 goroutine 同时读写共享 map 导致 panic(如 fatal error: concurrent map writes)或数据不一致。直接使用原生 map 不是线程安全的,必须配合同步机制。最常用、轻量且可控的方式是用 sync.RWMutex 配合 map —— 读多写少场景下性能优于全互斥锁。
为什么选 RWMutex 而不是 Mutex
sync.RWMutex 区分读锁(RLock)和写锁(Lock),允许多个 goroutine 同时读,但写操作会独占锁。对缓存这种「读远多于写」的典型场景,能显著提升并发吞吐。而纯 sync.Mutex 会让所有读操作排队,成为瓶颈。
基础结构体设计
定义一个带锁的缓存结构,封装 map 和读写锁:
</font><br><font color="#007acc">type</font> Cache <font color="#007acc">struct</font> {<br> mu sync.RWMutex<br> data map[string]interface{}<br>}<br><br><font color="#007acc">func</font> NewCache() *Cache {<br> <font color="#007acc">return</font> &Cache{<br> data: make(map[string]interface{}),<br> }<br>}<font color="#888">
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
关键方法实现(读/写/删除)
所有访问 data 的方法都必须加锁,且严格区分读写场景:
-
Get(读):用
RUnlock()+RLock(),允许并发读 -
Set(写):用
Lock()+Unlock(),确保写独占 - Delete(写):同 Set,需写锁
-
注意 nil 检查:在 Get 中判断 key 是否存在,避免返回零值造成歧义(例如想缓存
nil或false)
</font><br><font color="#007acc">func</font> (c *Cache) Get(key string) (value interface{}, ok <font color="#007acc">bool</font>) {<br> c.mu.RLock()<br> <font color="#007acc">defer</font> c.mu.RUnlock()<br> value, ok = c.data[key]<br> <font color="#007acc">return</font><br>}<br><br><font color="#007acc">func</font> (c *Cache) Set(key string, value interface{}) {<br> c.mu.Lock()<br> <font color="#007acc">defer</font> c.mu.Unlock()<br> c.data[key] = value<br>}<br><br><font color="#007acc">func</font> (c *Cache) Delete(key string) {<br> c.mu.Lock()<br> <font color="#007acc">defer</font> c.mu.Unlock()<br> <font color="#007acc">delete</font>(c.data, key)<br>}<font color="#888">
进阶考虑:避免锁粒度太粗
如果缓存条目极多、更新频繁,单把大锁可能成为瓶颈。可考虑分片(sharding):将 map 拆成多个子 map,每个配独立 RWMutex。例如按 key 哈希取模选择分片,降低锁冲突概率。标准库 sync.Map 就是类似思路的优化实现(但适用于更简单场景,不支持遍历或自定义过期)。

















