跳转到主内容
极星编程网:以代码为星,赴技术山海!

C++实现线程安全的Map容器 _ std::shared_mutex与读写锁封装【源码】

std::shared_mutex更适合读多写少的Map场景,因其支持多读单写并发,而std::mutex读写均互斥,会严重降低频繁查询时的并发读性能。 std::shared_mutex 为什么比 std::mutex 更适合 Map 的读多写少场景 因为
std::shared_mutex
允许多个线程同时读、但写独占,而
std::mutex
无论读写都互斥——对频繁查询的 Map 来说,后者直接抹杀并发读性能。 典型误用是:用
std::mutex
包裹整个
std::map
或
std::unordered_map
,结果所有
find()
调用排队等待,吞吐掉一半以上。 只在 C++17 及以上可用;C++14 项目需用 boost::shared_mutex 或自实现(不推荐)
lock_shared()
和
lock()
是阻塞调用,注意死锁风险(比如嵌套读锁+写锁) Windows 上 VS2015 起支持,但早期版本有性能缺陷;GCC 8+、Clang 7+ 表现稳定 封装线程安全 Map 的关键结构设计 不能直接继承
std::unordered_map
——STL 容器非虚析构,且内部无锁语义;应采用组合 + RAII 封装,把锁生命周期和操作绑定。 核心是提供两类接口:带
shared_lock
的只读方法(
get()
、
contains()
),和带
unique_lock
的读写方法(
put()
、
erase()
)。 立即学习 “ C++免费学习笔记(深入) ”; 避免暴露原始容器指针或引用,防止绕过锁直接访问 所有 public 方法必须保证异常安全:例如
put()
中若
insert()
抛异常,锁必须已释放(RAII 自动保证) 不要在锁内做耗时操作(如日志打印、网络调用),否则拖慢所有并发读
template class ThreadSafeMap { private: mutable std::shared_mutex mtx_; std::unordered_map map_; public: V get(const K& key) const { std::shared_lock lock(mtx_); auto it = map_.find(key); if (it != map_.end()) return it->second; throw std::out_of_range("key not found"); } void put(const K& key, const V& value) { std::unique_lock lock(mtx_); map_[key] = value; } };
std::shared_lock 和 std::unique_lock 的使用陷阱 常见错误是混用构造方式:
std::shared_lock lock(mtx_)
正确,但
std::shared_lock lock; lock.lock(mtx_)
会编译失败——
shared_lock
不支持默认构造后延迟加锁。 C知道 CSDN推出的一款AI技术问答工具 下载 另一个坑是作用域控制不当:如果把锁对象声明在 if 分支里,提前释放会导致后续访问裸奔。
std::shared_lock
构造时自动调用
lock_shared()
,析构时自动
unlock_shared()
std::unique_lock
同理,但调用的是
lock()
/
unlock()
想延迟加锁?用
std::defer_lock
标签:
std::unique_lock lock(mtx_, std::defer_lock); ... lock.lock();
别用
std::lock_guard
——它不支持
shared_mutex
性能敏感点:何时该用 std::shared_mutex,何时该换方案 当写操作占比超过 20%,或者单次写操作涉及大量元素(如批量插入 1000+ 条),
std::shared_mutex
的写锁开销可能反超收益——此时考虑分段锁(sharded map)或无锁结构(如 folly::AtomicHashMap)。 另外,
std::shared_mutex
在某些旧内核(如 Linux 3.16 之前)依赖 futex 的共享模式,实测争用激烈时延迟抖动明显。 压测建议:用
std::chrono::high_resolution_clock
测单次
get()
平均耗时,对比 1/4/16 线程并发下的变化趋势 Debug 模式下
shared_mutex
开销显著高于 Release,务必在 -O2 下验证 如果 Map 键值类型重载了
operator==
或哈希函数较慢,锁外耗时可能掩盖锁本身影响,先优化这些再调锁 真正难处理的是迭代器遍历:
for (auto& p : map_)
必须全程持
shared_lock
,但若遍历中调用外部函数,就容易因外部锁或阻塞导致长持有——这种场景更适合快照拷贝或读写分离设计。

相关文章