C++共享互斥锁两种使用方式的正确性、差异及优劣咨询
关于C++共享互斥锁两种写法的对比分析
嘿,咱们来好好拆解下你的问题:首先你的手动锁写法语法上是正确的,但从工程实践角度来说,存在不少隐患,和第一种RAII风格的写法差异很大,而且后者有着压倒性的优势。
先看你给出的两种实现:
第一种:RAII风格锁实现
class MyData { std::vector<double> data_; mutable std::shared_mutex mut_; // 保护data_的互斥锁 public: void write() { std::unique_lock<std::shared_mutex> lk(mut_); // ... 写入data_的操作 ... } void read() const { std::shared_lock<std::shared_mutex> lk(mut_); // ... 读取data_的操作 ... } };
第二种:手动调用锁API实现
class MyData { std::vector<double> data_; mutable std::shared_mutex mut_; public: void write() { mut_.lock(); // ... 写入data_的操作 ... mut_.unlock(); } void read() const { mut_.lock_shared(); // ... 读取data_的操作 ... mut_.unlock_shared(); } };
接下来详细说说两者的差异和各自的优劣:
核心差异与优势对比
1. 异常安全性(最关键的差异)
如果你的write或read函数里的业务代码抛出了异常,手动锁写法会直接“翻车”:
- 手动调用
lock()/lock_shared()后,一旦异常抛出,后续的unlock()/unlock_shared()根本不会执行,导致互斥锁被永久持有,其他线程永远无法获取锁,直接造成死锁。 - 而RAII风格的锁(
unique_lock/shared_lock)是基于对象生命周期管理的:锁对象lk在函数结束(不管是正常返回还是异常抛出)时都会被销毁,析构函数会自动调用unlock(),绝对不会出现锁泄漏的情况。
2. 代码的可读性与维护性
- RAII写法把锁的作用范围和锁对象的生命周期绑定,只要看锁对象的创建和销毁范围,就能清晰知道这段代码是被锁保护的,逻辑一目了然。
- 手动锁需要你自己严格配对
lock和unlock,一旦后续修改代码(比如在中间加了一个提前返回的return语句),很容易忘记在分支里加unlock,埋下锁泄漏的隐患,而且这种问题很难排查。
3. 灵活性与功能扩展性
unique_lock和shared_lock提供了很多手动锁没有的便捷功能:
- 可以用
defer_lock参数延迟加锁,后续手动调用lock(),适合需要先做一些准备工作再加锁的场景; - 支持
try_lock()尝试加锁,避免线程阻塞; - 支持移动语义,可以把锁的所有权从一个对象转移到另一个对象,实现更复杂的锁管理逻辑。
这些功能手动调用mutex的API虽然也能实现,但RAII封装后代码更简洁安全,不容易出错。
总结
你的手动锁写法语法正确,但在工程实践中几乎不推荐使用,第一种RAII风格的写法是C++并发编程里的标准最佳实践,在异常安全、可维护性、灵活性上都有着绝对的优势,是处理互斥锁的首选方式。
内容的提问来源于stack exchange,提问作者MMM
相关产品推荐
相关产品推荐

