go标准库无lru缓存,需用container/list+map组合实现;关键在显式初始化map、entry含key字段、get用rlock保护查找与movetofront、put用lock并严格按“删map再删list”顺序淘汰。

Go 标准库没有 lru,但用 container/list + map 组合能写出 50 行以内、无第三方依赖、逻辑透明的实现——关键不是“能不能写”,而是“并发下不 panic、淘汰不漏删、容量不失控”。
为什么 map[string]*list.Element 必须显式初始化
常见错误是只声明没初始化:cache map[string]*list.Element,然后直接 cache[key] = elem,结果 panic: assignment to entry in nil map。
- 必须在构造函数里调用
make(map[string]*list.Element) -
*list.Element持有值指针,若值是结构体且含[]byte或map[string]int,注意浅拷贝:Put 时别直接赋值原结构体,应深拷贝或重建 - 不初始化就 Get,会返回零值+false;但 Put 前未检查容量边界,会导致
list.Len()超限后无法自动淘汰
Get 中调用 MoveToFront 为什么不能省略
如果只查 map、取 value,却不调用 list.MoveToFront(elem),链表顺序不会更新,下次淘汰时会把“刚被访问过”的项干掉。
- 正确顺序:查 map → 取
elem→list.MoveToFront(elem)→ 返回elem.Value - 错误写法:
val := elem.Value; elem.Value = newVal后不MoveToFront,排序失效 - 即使只读访问
elem.Value,也必须在锁保护下进行——Remove可能在另一 goroutine 发生,导致Value变成nil
并发安全加锁位置怎么选才不拖慢读性能
把整个 Get 包进 mu.Lock() 是典型错误:所有读请求排队,缓存反而成瓶颈。
-
Get只需mu.RLock(),且仅保护两件事:map 查找 +list.MoveToFront -
Put必须用mu.Lock(),因涉及 map 写入、list.PushFront、以及可能触发的RemoveOldest - 绝对不要在锁内做 HTTP 请求、DB 查询、或任何不可控耗时操作——缓存层只搬运数据
容量控制失效的三个隐蔽原因
LRU 最常出问题的不是算法逻辑,而是边界没卡死。
- 淘汰前没判断
list.Len() >= capacity,而是用==,导致容量为 1 时首次 Put 就满,第二次 Put 才淘汰——实际多存了一项 -
RemoveOldest里只删list不删map,造成内存泄漏(map里 key 还在,但对应Element已被移除) - 没在
entry结构体里存key字段,导致淘汰时无法反查 key 并从 map 删除——必须定义为type entry struct { key string; value interface{} }
真正难的不是写完,是让 list.Element 的生命周期和 map 键值绑定严丝合缝,且所有路径都覆盖容量边界和并发竞争。漏掉任意一环,上线后就是偶发 panic 或缓存项越积越多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











