std::shared_mutex更适合缓存读写分离,因其支持多读并发、单写独占;splice()需传入源list对象且迭代器操作须在锁内;淘汰节点须先删map再删list以避免悬空迭代器。

不能只用一把 std::mutex 锁住整个 get() 和 put(),否则读多写少场景下吞吐量会断崖式下降。
为什么 std::shared_mutex 比 std::mutex 更适合缓存读写分离
真实业务中 get() 占比常超 90%,而 std::mutex 强制所有线程串行排队,哪怕只是并发读,也完全无法利用多核。但 std::shared_mutex 允许多个 std::shared_lock 同时持有(即并发读),仅在 put() 时由 std::unique_lock 独占——这正是缓存的典型访问模式。
需注意平台兼容性:
- Linux GCC 8+ 默认支持;
- macOS Clang 需显式加
-std=c++17 -D_GLIBCXX_CONCEPTS; - Windows MSVC 2015u3+ 支持,但底层基于 SRWLock,持续写入时读者可能饥饿。
std::list::splice() 的参数顺序和迭代器有效性
get() 中把节点移到链表头部必须用 splice(),但常见错误是漏掉第二个参数或传错类型:
立即学习“C++免费学习笔记(深入)”;
- 错误写法:
cache_list_.splice(cache_list_.begin(), it->second)—— 编译可能通过,但行为未定义; - 正确写法:
cache_list_.splice(cache_list_.begin(), cache_list_, it->second)—— 第二个参数必须是源std::list对象本身; -
it->second是std::list::iterator,不是指针,不可解引用后传; - 所有
splice()操作必须在锁内完成,否则其他线程erase()后该迭代器立即失效。
淘汰尾部节点时,unordered_map 和 list 的擦除顺序
顺序反了会导致悬空迭代器:若先 pop_back(),被删节点的迭代器失效,但 cache_map_[key] 仍指向它,下次 get(key) 解引用即未定义行为(UB)。
必须严格按以下三步执行:
- 取尾部键值对:
auto last = cache_list_.back(); - 从 map 中擦除:
cache_map_.erase(last.first); - 再从 list 中移除:
cache_list_.pop_back()。
这个顺序保证 cache_map_ 中永远不存指向已销毁节点的迭代器。
真正容易被忽略的是:所有涉及链表节点移动或删除的操作,都必须确保迭代器生命周期与锁范围严格对齐——不是“加锁→查→解锁→操作”,而是“加锁→查→操作→解锁”。稍一松懈,竞态就藏在 splice 或 erase 的毫秒之间。


















