c++中如何实现简单的互斥锁封装_c++ lock_guard与unique_lock【详解】

8次阅读

应使用 RaiI 封装(如 std::lock_guard 或 std::unique_lock)而非手动调用 std::mutex 的 lock()/unlock(),以防异常、提前返回等导致未解锁而引发死锁或数据竞争;std::lock_guard 适用于简单临界区,构造即加锁、析构自动解锁;std::unique_lock 更灵活但开销略大,支持延迟加锁、尝试加锁、条件变量等待和所有权转移。

c++中如何实现简单的互斥锁封装_c++ lock_guard与unique_lock【详解】

为什么不能直接用 std::mutex 做手动加锁/解锁

手动调用 lock()unlock() 极易出错:异常抛出、提前 return、逻辑分支遗漏都会导致锁未释放,引发死锁或数据竞争。c++ 标准库不鼓励这种写法,连 std::mutexunlock() 都要求必须由同一线程调用,且仅在已加锁状态下才合法——一旦违反,行为未定义。

正确做法是依赖 RAII:对象构造时加锁,析构时自动解锁。这就是 std::lock_guardstd::unique_lock 存在的根本原因。

std::lock_guard 适合什么场景

它是最轻量、最安全的互斥锁封装,只支持构造时加锁、析构时解锁,不支持延迟加锁、转移所有权或条件等待。

  • 适用于「进入作用域即需锁定,离开即释放」的简单临界区
  • 构造函数必须传入一个可加锁的 std::mutex(或兼容类型,如 std::recursive_mutex
  • 没有默认构造函数,不可复制,但可移动(不过移动后原对象不再持有锁
  • 性能开销最小,编译器容易优化
std::mutex mtx; int counter = 0;  void increment() {     std::lock_guard lock(mtx); // 构造即 lock()     ++counter; // 临界区 } // 离开作用域,lock 析构,自动 unlock()

std::unique_lock 多出来的能力和代价

它比 lock_guard 更灵活,但也更重。核心区别在于:它管理的是「锁的状态」,而不仅是「加锁动作」。

立即学习C++免费学习笔记(深入)”;

  • 支持延迟加锁:std::unique_lock<:mutex> lock(mtx, std::defer_lock);,之后再调用 lock.lock()
  • 支持尝试加锁:if (lock.try_lock()) { ... }
  • 支持条件变量:std::condition_variable::wait(lock, pred) 要求传入 unique_lock
  • 支持转移所有权:std::move(lock1)lock2,原 lock1 变为空状态
  • 析构时若仍持有锁,会自动释放;若为空(如被 move 走或从未 lock 过),则无操作

注意:灵活性带来额外成员变量(如是否拥有锁、指向 mutex 的指针等),内存占用略大,且部分操作(如 try_lock)可能有轻微性能成本。

std::mutex mtx; std::condition_variable cv; bool ready = false;  void waiter() {     std::unique_lock lock(mtx);     cv.wait(lock, []{ return ready; }); // wait 内部会临时 unlock,唤醒后重新 lock     // 此处 lock 已重新持有 }

常见误用与陷阱

很多问题不是语法错误,而是语义误解:

  • std::lock_guardstd::unique_lock 都只是 RAII 封装,它们本身不拥有 std::mutex;多个 guard 持有同一 mutex 是允许的,但必须确保不发生嵌套加锁(除非是 std::recursive_mutex
  • std::unique_lock 当作「可复制的锁」用:它不可复制,复制会编译失败;移动后原对象失效,再次使用(如 lock.unlock())是未定义行为
  • Lambda 捕获中按值捕获 std::unique_lock:这会触发移动,导致原作用域锁被释放,极易引发竞态
  • 误以为 std::unique_lock 构造时不加锁就等于“没风险”:延迟加锁后若忘记调用 lock(),后续访问临界资源就是裸奔

真正需要多线程安全的临界区,长度要尽可能短;锁的粒度要尽量细;而选择 lock_guard 还是 unique_lock,取决于你是否需要它提供的那几个额外能力——不需要就别用,避免引入不必要的复杂性和开销。

text=ZqhQzanResources