container/list + map 无法直接复用为 LRU-K,因其仅支持单次访问记录,缺少历史队列和访问计数器;混用会导致晋升逻辑错乱、计数不同步及并发竞态,必须拆分为两个隔离链表+两套map并分层加锁。

为什么 container/list + map 无法直接复用为 LRU-K
标准库的 container/list 和 map 组合能高效实现 LRU-1,但直接套用到 LRU-K 会失效——因为 LRU-K 要求每个 key 至少被访问 K 次才晋升进主缓存,而标准双链表结构里没有“历史队列”和“访问计数器”这两个关键组件。
常见错误现象包括:
-
Get一次就命中,实际应至少访问K次才进主缓存 - 历史队列满后未淘汰最老 entry,导致
cnt膨胀、内存泄漏 -
cnt和historyCache.mp不同步:key 在 history 队列里,但计数已丢失或未更新
必须拆成两个物理隔离的链表 + 两套 map
LRU-K 的核心是双层结构:一层主缓存(LRU 行为),一层历史队列(FIFO 行为)。混用会导致晋升逻辑错乱,比如刚访问第 1 次就进了主链表头,后续根本无法判断是否达 K 次。
正确组织方式如下:
立即学习“go语言免费学习笔记(深入)”;
- 主缓存:
ll *list.List+mp map[string]*list.Element,行为同标准 LRU:命中则MoveToFront,满则RemoveOldest - 历史队列:
historyCache.ll *list.List+historyCache.mp map[string]*list.Element+historyCache.cnt map[string]int,仅用于计数,不存 value - 晋升逻辑放在
Get:若historyCache.cnt[key] >= K,则从 history 队列中Remove该节点,并调用Put(key, value)进主缓存(注意:value 需业务层提供或提前缓存) -
historyCache.ll是 FIFO,淘汰用Front(),不是Back();它只管“谁先进来”,不管“谁最近用”
淘汰时如何高效选出「第 K 次访问最久」的 key
不能每次淘汰都遍历全量 keys 并计算每个的 history[0](O(N) 太重)。正确做法是维护一个最小堆(min-heap),堆元素是 {key string, kthTime time.Time},按 kthTime 排序。
关键细节:
- 每次 key 历史更新后,把新
kthTime推入堆 —— 注意堆里可能有多个同 key 的过期条目,所以要用「懒删除」 - 淘汰时
Pop出堆顶,检查其kthTime是否与当前 key 的history[0]一致,不一致就丢弃并继续Pop - Go 标准库
container/heap要求实现heap.Interface,重点写好Less(i, j int) bool和Swap、Push、Pop - 每个 key 对应的访问历史建议用
type accessHistory []time.Time升序存储(history[0]是第 K 次访问时间),插入时append(history, now)再截断头部:if len(history) > K { history = history[1:] }
并发安全下锁怎么分层才不卡 Get
LRU-K 比纯 LRU 多一层 history 访问,锁粒度更敏感。一把全局 sync.Mutex 会让所有 Get 阻塞,尤其未命中路径要查主缓存 → 查 history → 更新计数 → 判晋升 → 可能 Put,全程串行。
推荐分层加锁策略:
- 主缓存读(
Get命中路径):用sync.RWMutex.RLock(),只保护mp查找和ll.MoveToFront() - 主缓存写(
Put/RemoveOldest):升级为Lock(),但临界区只做链表指针重连 +mp增删,绝不含 value 序列化、日志、回调 - history 部分:单独一把
sync.RWMutex(如叫historyMu),因为它的读写频率和主缓存不一致;Get未命中时先historyMu.RLock()查cnt,再Lock()更新计数或移出队列 - 切忌在
Get中先Unlock()主锁再去处理 history —— 这会导致竞态:A goroutine 刚查完主缓存未命中,正准备去 history 查,B goroutine 此时Put同 key 进主缓存,A 就会重复晋升
Get 未命中且满足 cnt[key] >= K 时,显式触发一次 Put(key, value)——这个 value 必须由上层业务提供,或者你得提前缓存好原始数据源,否则晋升会失败。


















