用 std::chrono::system_clock::time_point 存绝对过期时间最可靠,避免相对秒数在跨天、夏令时或系统时间回拨时出错;校验时直接比较 time_point,高效且线程安全。

std::chrono 怎么存过期时间才不踩精度坑
直接用 std::chrono::system_clock::now() 存绝对时间,比存相对秒数更可靠。因为相对时间在跨天、夏令时或系统时间回拨时会出错;而 std::chrono::time_point 是单调递增的(只要没手动改系统时间),校验时用 now() 即可,无需换算。
常见错误是把过期时间存成 long long 秒或毫秒——这容易溢出(比如 2038 年问题),也丢失类型语义,后续加减、比较都得反复 cast。
- 用
using TimePoint = std::chrono::steady_clock::time_point;(推荐)或system_clock::time_point,前者不受系统时间跳变影响,适合做超时判断 - 设置过期时间统一用
clock::now() + std::chrono::seconds(300),别手算毫秒再构造 - 读缓存前必须先检查
expire_time ,且这个判断要放在锁内(避免检查和取值之间过期)
怎么安全地清理过期项:惰性删除 vs 后台定时扫描
每次 get() 都检查过期并删,叫惰性删除——实现简单、无额外线程、内存释放及时,但高并发下重复判断开销略大;后台起一个 std::thread 定期扫描,叫主动清理——能批量回收,但引入竞态风险(比如清理线程正删,用户线程正读)和资源占用。
实际项目中,除非缓存项生命周期极长(>1 小时)且数量极大(>10 万),否则优先选惰性删除。它足够快,且完全规避了多线程同步复杂度。
立即学习“C++免费学习笔记(深入)”;
- 惰性方案:在
get(key)内部,查到 entry 后立刻用if (entry.expire_time - 如果非要后台清理,别用
sleep_for硬等——用std::condition_variable配合wait_until,让线程在下次最早过期时间点唤醒 - 清理线程访问
std::unordered_map必须加同一把std::shared_mutex(读写锁),且写操作(erase)需独占,读操作(遍历)可共享
std::shared_mutex 在 C++17 下怎么用才不出 undefined behavior
std::shared_mutex 不是所有编译器都默认支持:GCC 8+、Clang 7+、MSVC 2019 起可用,但需确保编译选项含 -std=c++17;若用旧标准,只能退回到 std::mutex,吞并发性能损失。
典型误用是混用 lock_guard 和 shared_lock 持有同一把锁——会导致死锁或 UB。读操作必须用 std::shared_lock<:shared_mutex></:shared_mutex>,写操作必须用 std::unique_lock<:shared_mutex></:shared_mutex> 或 std::lock_guard。
- get() / contains() 等只读操作:用
std::shared_lock lock(mutex_); - set() / erase() 等写操作:用
std::unique_lock lock(mutex_); - 绝不能在一个作用域里同时持有一个
shared_lock和一个unique_lock——即使它们锁的是不同变量,也可能触发静态分析误报,且逻辑上不合理
为什么不要自己实现“定时器任务”来驱逐缓存
所谓“定时器任务”,指起一个线程,每 100ms 扫一遍全部 key 判断是否过期。这看似主动,实则低效又危险:遍历 unordered_map 是 O(n) 行为,哪怕只有 1 万个 key,每秒 10 次就是 10 万次比较;更糟的是,遍历时若其他线程正在插入/删除,unordered_map 可能重哈希,迭代器失效,直接 crash。
真正需要定时能力的场景(如统计上报、健康检查),应该单独建一个轻量 timer loop,和缓存数据结构解耦。缓存本体只负责“存、取、查过期”,不承担调度职责。
- 如果你看到开源库用
std::thread + while(true) + this_thread::sleep_for清缓存——那大概率是玩具代码,上线前务必重写 - 想精确控制清理节奏?在
set()时记录全局最早过期时间,用condition_variable::wait_until唤醒清理线程,只扫可能过期的子集 - 最省心的做法:不做后台清理,靠业务层调用
get()自然淘汰,配合 LRU 驱逐策略防内存暴涨
过期时间的语义一致性比“看起来很准时”重要得多——宁可让某条数据多活 200ms,也不要因时钟跳变或竞态导致提前丢数据或永远不删。所有时间判断必须基于同一时钟源,所有清理动作必须与数据访问共享同一把锁粒度。


















