std::lock_guard通过raii机制自动加锁和解锁:构造时调用lock(),析构时必调unlock(),确保异常安全;必须在临界区作用域内声明命名对象,不可为全局/静态、不可拷贝,多互斥量应优先用std::scoped_lock。

std::lock_guard 为什么能自动加锁又自动解锁
因为它的构造函数立刻调用 mutex.lock(),析构函数(对象生命周期结束时)必然调用 mutex.unlock() —— 这是 RAII 的核心:资源获取即初始化,资源释放即析构。不用手动配对调用,哪怕中间抛异常,析构也会触发。
常见错误现象:std::mutex 被多个线程重复 lock() 导致死锁,或忘记 unlock() 引发后续线程永久阻塞;用 if 分支提前 return 却漏掉解锁,也是高频 bug。
- 必须在临界区作用域内声明
std::lock_guard对象(比如函数开头、{}块内),不能只声明不定义 - 不能把
std::lock_guard声明为全局或静态变量 —— 它的生命周期无法与具体临界区对齐 - 不要试图拷贝
std::lock_guard:它被显式删除了拷贝构造和赋值操作,只能移动(但通常也不需要移动)
std::lock_guard 和 std::unique_lock 选哪个
std::lock_guard 是轻量、一次性、不可重入的守卫;std::unique_lock 更灵活,支持延迟锁定、条件变量配合、可转移所有权,但也带来额外开销和误用风险。
使用场景判断:
- 只需要“进块就锁、出块就放” → 无条件选
std::lock_guard - 要调用
cv.wait()或cv.wait_for()→ 必须用std::unique_lock,因为std::lock_guard不支持解锁后重新锁定 - 需要在某个条件下才加锁(比如先检查再决定是否进临界区)→ 用
std::unique_lock配合try_lock() - 性能敏感且逻辑简单 →
std::lock_guard构造/析构更快,无状态缓存,更易被编译器优化
多个互斥量怎么安全加锁避免死锁
直接按任意顺序对多个 std::mutex 构造 std::lock_guard 可能引发死锁(线程 A 锁 m1 后等 m2,线程 B 锁 m2 后等 m1)。C++17 起推荐用 std::scoped_lock 替代多参数 std::lock_guard。
示例:
std::mutex m1, m2; // ✅ 正确:自动按统一顺序加锁,无死锁风险 std::scoped_lock lock(m1, m2); // ❌ 错误:两个 lock_guard 独立构造,顺序不确定 std::lock_guard guard1(m1); std::lock_guard guard2(m2); // 可能卡住
-
std::scoped_lock是std::lock_guard的多互斥量安全升级版,C++17 起可用 - 如果必须用 C++11/14,可用
std::lock(m1, m2)+std::adopt_lock组合,但写法更冗长、易错 - 所有涉及多个 mutex 的临界区,只要它们可能被不同线程以不同顺序访问,就必须统一用
std::scoped_lock或等效机制
容易被忽略的生命周期陷阱
std::lock_guard 的有效性完全依赖其对象是否还存活。最隐蔽的问题是:把守卫对象绑定到临时值、或放在错误的作用域里。
- 错误写法:
std::lock_guard<:mutex>(m);</:mutex>—— 这是创建一个临时对象,构造完立刻析构,锁随即释放 - 错误写法:在 if 分支里声明,但临界区逻辑写在分支外 → 守卫提前销毁,失去保护
- 正确写法:命名对象,并确保它比所有受保护代码活得更久,例如放在函数开头或最小必要作用域的花括号内
- 注意
std::mutex本身不能被拷贝,也不能跨线程传递;std::lock_guard持有的只是引用,所以它本身可以按值传入函数(但通常没必要)
RAII 不是魔法,它只保证“对象析构时释放”,而对象何时析构,取决于你把它放在哪——这点比语法细节更容易出问题。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











