不能只用sync.map实现lru,因其无访问顺序记录、get后无法原子移动至链表头、淘汰需o(n)遍历且易并发异常;必须用container/list+map组合,满足map值为*list.element、element.value为含key/value的结构体、map显式初始化三条件。

为什么不能只用 sync.Map 实现 LRU
因为 sync.Map 没有访问序,Get 后无法把对应节点 O(1) 移到链表头;容量超限时要淘汰最久未用项,必须遍历全部 key,高并发下锁竞争剧烈,且时间戳更新容易漏或不同步。常见错误是:给 sync.Map 加个时间字段然后手动排序——这既不是真正的 LRU,也扛不住并发读写。
container/list + map 组合的关键约束
必须满足三个硬性条件,缺一不可:
-
map的 value 类型必须是*list.Element,不能是interface{}或裸值 - 每个
list.Element.Value必须是含key和value字段的结构体(如type entry struct { key string; value interface{} }),否则淘汰时无法反查 key 并清理 map -
map必须显式初始化:cache := make(map[string]*list.Element),只声明不make会导致panic: assignment to entry in nil map
sync.RWMutex 锁粒度怎么设才不拖慢性能
读多写少是缓存典型场景,锁粗了就成瓶颈。错误做法是整个 Get 方法包在 mu.Lock() 里;正确拆分是:
-
Get只用mu.RLock(),但必须覆盖cache[key]查找 +list.MoveToFront()两步(MoveToFront不在锁内会引发竞态) -
Put必须用mu.Lock(),因为涉及map写入、list.PushFront()、以及可能触发的RemoveOldest - 所有对
list.Element.Value的访问(哪怕只是读)都必须在锁保护下进行——list.Remove()后该指针仍存在,但Value可能已被回收,不加锁断言会 panic 或读脏数据
淘汰逻辑顺序错了就会出问题
容量满时从 list.Back() 开始删,顺序必须严格是:先取元素 → 断言结构体 → delete(cache, ent.key) → list.Remove(e)。反着来(先 Remove 再 delete)会导致:
- 其他 goroutine 还持有该
*list.Element指针,e.Value已被释放却还在被类型断言,可能 panic 或返回零值 -
map中残留 key 指向已失效的Element,后续Get命中后访问e.Value出错 -
entry结构体里的value若是大对象(如[]byte),没及时清理还可能引发内存泄漏
最容易被忽略的是:哪怕你封装得再严实,只要暴露了 *list.Element 或没在锁内做 ok 判断,类型断言失败就静默返回零值——缓存“看起来生效”,实际数据丢了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











