lock_guard(m, adopt_lock)必须放在作用域的最后吗
·
结论先行:不必须,而且“放在作用域最后”这个说法本身就是个误解。
真正的规则只有一句话 👇
lock_guard(m, adopt_lock)必须在「已经成功加锁之后」构造,并且它的作用域要覆盖你希望持锁的代码区间。
下面把这个问题讲透。
1️⃣ 为什么会有人觉得“必须放在作用域最后”?
很多人看到这种写法:
std::lock(m1, m2);
std::lock_guard<std::mutex> lg1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lg2(m2, std::adopt_lock);
// 临界区
于是误以为:
“哦,
lock_guard(adopt_lock)好像只能在最后写?”
其实不是。
2️⃣ 正确理解:不是“最后”,而是“之后”
✅ 正确顺序(唯一硬性要求)
m.lock(); // 或 std::lock(...)
std::lock_guard<std::mutex> lg(m, std::adopt_lock);
只要满足:
lg构造时m已经被当前线程锁住
👉 放在作用域哪一行都行。
3️⃣ 举几个对比示例
✅ 合法:不是最后一行
void f() {
std::lock(m);
int x = calc(); // 还没用到锁也没关系
std::lock_guard<std::mutex> lg(m, std::adopt_lock);
// 临界区
}
完全 OK。
❌ 错误:在加锁之前构造
void f() {
std::lock_guard<std::mutex> lg(m, std::adopt_lock); // ❌ UB
std::lock(m);
}
问题:
lg析构时会unlock- 但你根本没 lock 过
- 👉 未定义行为
4️⃣ 真正需要你关注的是「作用域覆盖范围」
lock_guard 的生命周期 = 锁的持有时间
你想锁多久,就让对象活多久
{
std::lock(m);
std::lock_guard<std::mutex> lg(m, std::adopt_lock);
critical(); // 受保护
}
// 这里自动 unlock
如果你提前离开作用域,锁就提前释放
{
std::lock(m);
std::lock_guard<std::mutex> lg(m, std::adopt_lock);
}
// 锁已释放
other(); // 不再受保护
5️⃣ 常见“看起来像必须放最后”的原因
原因 1:RAII 的可读性习惯
很多团队习惯:
lock();
lock_guard(...);
// 临界区
只是为了 让读代码的人一眼明白:从这里开始持锁
原因 2:避免“空窗期”
不推荐这样:
std::lock(m);
// ……很多代码……
std::lock_guard<std::mutex> lg(m, std::adopt_lock);
因为中间如果:
returnthrowgoto
👉 锁会泄漏
6️⃣ 所以真正的最佳实践是?
✅ 紧挨着写(不是“最后”,而是“立刻”)
std::lock(m1, m2);
std::lock_guard<std::mutex> lg1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lg2(m2, std::adopt_lock);
// 临界区
理由:
- 不留异常/return 空窗
- 生命周期清晰
- 不容易被误用
7️⃣ 现代 C++:一行解决所有问题(强烈推荐)
如果你能用 C++17:
std::scoped_lock lock(m1, m2);
- 没有
adopt_lock - 没有顺序问题
- 没有作用域歧义
- 几乎不可能写错
8️⃣ 一句话总结(记住这个)
❌
lock_guard(m, adopt_lock)不是必须放在作用域最后
✅ 必须在锁已被当前线程持有之后立刻构造,并让它的作用域覆盖临界区
更多推荐

所有评论(0)