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

为什么不能直接用 std::mutex 做手动加锁/解锁
手动调用 lock() 和 unlock() 极易出错:异常抛出、提前 return、逻辑分支遗漏都会导致锁未释放,引发死锁或数据竞争。C++ 标准库不鼓励这种写法,连 std::mutex 的 unlock() 都要求必须由同一线程调用,且仅在已加锁状态下才合法——一旦违反,行为未定义。
正确做法是依赖 RAII:对象构造时加锁,析构时自动解锁。这就是 std::lock_guard 和 std::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_guard和std::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,取决于你是否需要它提供的那几个额外能力——不需要就别用,避免引入不必要的复杂性和开销。











