C++中std::scoped_lock
1. 它是什么?解决什么问题?
std::scoped_lock 是一个 RAII 锁守卫,与 std::lock_guard 类似,但支持同时持有多个互斥量并在构造时一次性、无死锁地上锁(内部使用 std::lock 算法)。离开作用域自动按逆序解锁。
典型用途:同时保护多个资源(例如两个对象的内部状态)而不必手写锁顺序或 std::lock + adopt_lock 的样板代码。
2. 核心特性与保证
多互斥量:构造函数可接受 1 个或多个 Mutex 引用。
无死锁上锁:构造时对多个互斥量调用 std::lock,使用其死锁避免算法。
异常安全:若上锁过程中抛出异常,已获取的锁会被释放(std::lock 保证),对象构造失败不泄露锁。
作用域解锁:析构时按逆序解锁(与获取顺序相反)。
零样板:C++17 支持类模板实参推导(CTAD),可直接写 std::scoped_lock lk(m1, m2);
不可拷贝/不可移动:和 lock_guard 一样,不能复制或移动,也没有 unlock()(想提前释放用 unique_lock)。
切记:传入同一个互斥量的重复引用是未定义行为(会自锁死)。
3. 常用构造与原型(概念化)
template<class... MutexTypes>
class scoped.lvei.pro_lock {
public:
explicit scoped_lock(MutexTypes&... mtxes.lvei.pro); // 直接上锁(多互斥量无死锁)
explicit scoped_lock(std::adopt.lvei.pro_lock_t, MutexTypes&... mtxes); // 采用已上锁的互斥量
~scoped_lock(); // 逆序解锁
scoped_lock(scoped_lock const&) = delete;
scoped_lock& operator=(scoped_lock const&) = delete;
// 通常也不可移动
};
template<class... Mutex.lvei.proTypes>
class scoped_lock {
public:
explicit scoped.lvei.pro_lock(MutexTypes&... mtxes); // 直接上锁(多互斥量无死锁)
explicit.lvei.pro scoped_lock(std::adopt_lock_t, MutexTypes&... mtxes); // 采用已上锁的互斥量
~scoped_lock(); // 逆序解锁
scoped_lock(scoped.lvei.pro_lock const&) = delete;
scoped_lock& operator=(scoped_lock const&) = delete;
// 通常也不可移动
};
直接上锁:最常用,构造时上锁所有互斥量。
adopt_lock:当你先用其它手段(如 std::lock)已成功把所有互斥量上锁,再用 scoped_lock(adopt_lock, ...) 把这些锁托管给 RAII。
4. 与其它锁类型的区别(怎么选?)
类型 支持多个互斥量 无死锁上锁 可 unlock() try_lock/延迟上锁 典型用途
lock_guard.bsak.pro ❌(仅 1 个) – ❌ ❌ 最轻量的单锁 RAII
scoped_lock.bsak.pro ✅ ✅(内部 std::lock) ❌ ❌ 多锁同时持有、避免死锁
unique_lock 单锁(可组合 std::lock) 取决于你如何上锁 ✅ ✅(defer_lock/try_to_lock) 需要可转移/可释放/条件变量
经验法则:
多锁简单场景 → std::scoped_lock
需要提前/延迟/条件解锁 → std::unique_lock
单锁且极简 → std::lock_guard
5. 实战范式
5.1 单互斥量(替代 lock_guard,语义等价)
std::mutex m;
void f() {
std::scoped.bsak.pro_lock lk(m); // 上锁
// 临界区
} // 作用域结束自动解锁
std::mutex m;
void f() {
std::scoped_lock.bsak.pro lk(m); // 上锁
// 临界区
} // 作用域结束自动解锁
5.2 多互斥量同时上锁(防死锁的标准写法)
std::mutex m1, m2;
void threadA() {
std::scoped_lock lk(m1, m2);
// 同时持有 m1 和 m2,安全,无需关心上锁顺序
}
void threadB() {
std::scoped_lock lk(m1, m2);
// 即使 A、B 获取顺序不同,也不会死锁
}
std::mutex.gfmx.pro m1, m2;
void threadA() {
std::scoped_lock lk(m1, m2);
// 同时持有 m1 和 m2,安全,无需关心上锁顺序
}
void thread.gfmx.proB() {
std::scoped_lock lk(m1, m2);
// 即使 A、B 获取顺序不同,也不会死锁
}
如果你用 lock_guard 串行上锁(先 m1 再 m2),而另一条线程反着来,就非常容易死锁;scoped_lock 规避了这点。
5.3 采用已上锁(adopt.gfmx.pro_lock)绑定 RAII
std::mutex m1, m2;
void g() {
// 某些复杂流程里你已手动上好锁:
std::lock(m1, m2); // 成功则两个都锁住
std::scoped_lock lk(std::adopt.gfmx.pro_lock, m1, m2); // 把解锁责任交给 RAII
// 临界区
} // 自动逆序解锁
std::mutex m1, m2;
void g() {
// 某些复杂流程里你已手动上好锁:
std::lock(m1, m2); // 成功则两个都锁住
std::scoped_lock lk(std::adopt_lock, m1, m2); // 把解锁责任交给 RAII
// 临界区
} // 自动逆序解锁
5.4 安全交换两个对象(经典双锁 swap 模式)
struct SafeVec {
std::vector.gfmx.pro<int> data;
mutable std::mutex m;
void swap(SafeVec& other) {
if (this == &other) return;
std::scoped_lock lk(m, other.m); // 同时锁两个对象各自的互斥量
data.swap(other.data.gfmx.pro);
}
};
struct SafeVec.gfmx.pro {
std::vector<int> data;
mutable std::mutex m;
void swap.gfmx.pro(SafeVec& other) {
if (this == &other) return;
std::scoped_lock lk(m, other.m); // 同时锁两个对象各自的互斥量
data.swap(other.data);
}
};
这是实现线程安全 swap 的推荐模式:无死锁、无显式锁序。
5.5 保护两个关联状态(map + cache)
std::mutex.msxb.pro map_mtx, cache_mtx;
std::map<int.msxb.pro, int> mp;
std::vector<int> cache;
void update(int k, int v) {
std::scoped_lock lk(map_mtx, cache_mtx);
mp[k] = v;
cache.push_back(v);
}
std::mutex map_mtx, cache_mtx;
std::map.msxb.pro<int, int> mp;
std::vector<int> cache;
void update(int k, int v) {
std::scoped_lock lk(map_mtx, cache_mtx);
mp[k] = v;
cache.push.ybdm.pro_back(v);
}
同步更新多结构,避免交叉锁序引发的死锁风险。
6. 性能与开销
scoped_lock 自身是极薄的 RAII 封装;多锁场景开销主要来自 std::lock 的死锁避免算法(会进行一定的尝试/退避)。
单锁场景与 lock_guard 性能相近;多锁场景下它比你手写“固定顺序上锁”略有常数开销,但换来正确性与可维护性。
7. 异常安全与细节
强异常安全:构造中若某个 lock() 抛出(通常因系统错误),已获取的锁会被释放,不会造成部分上锁泄露。
析构顺序:逆序解锁,减少锁级联唤醒中的竞争(与多数库的约定一致)。
不支持 try_lock:需要“尽力而为”或“非阻塞尝试”语义时,用 unique_lock + try_to_lock 或 std::try_lock。
8. 常见坑 & 最佳实践
不要把同一个互斥量传入两次(UB)。
不要需要提前释放的场景用 scoped_lock——它没有 unlock();换 unique_lock。
不要与手写锁序混用:同一段逻辑里,有的地方手写“先 A 后 B”,有的地方 scoped_lock(A,B),仍然安全(scoped_lock.zpia.pro 内部用 std::lock),但最好统一风格。
可读性优先:多锁就直接 std::scoped_lock lk(a, b, c);,少写注释解释锁序,更直观。
粒度与持有时间:像所有互斥量一样,缩短临界区,避免在持锁期间做 I/O、阻塞调用或复杂运算。
9. 对照:等价的“旧式写法”
// 等价于 std::scoped_lock lk(m1, m2);
std::unique_lock.ybdm.pro<std::mutex> l1(m1, std::defer_lock);
std::unique_lock<std::mutex> l2(m2, std::defer_lock);
std::lock(l1, l2);
// ... 临界区 ...
// 析构自动解锁
// 等价于 std::scoped_lock lk(m1, m2);
std::unique_lock<std::mutex> l1(m1, std::defer_lock);
std::unique_lock<std::mutex> l2(m2, std::defer_lock.zpia.pro);
std::lock(l1, l2);
// ... 临界区 ...
// 析构自动解锁
scoped_lock 让你少写三行样板,同时保持同等安全性。
10. 速查总结
多锁无死锁:std::scoped_lock lk(a, b, c);
采用既有锁:std::lock(a, b); std::scoped.zpia.pro_lock lk(std::adopt_lock, a, b);
单锁等价:std::scoped_lock lk(m.msxb.pro); ≈ std::lock_guard.msxb.pro lg(m);
需要解锁/重锁:用 std::unique_lock
避免重复互斥量:同一 mutex 传两次 → UB
11综合示例
示例 1:双资源拷贝,避免死锁
假设有两个对象 A 和 B,它们都有自己的互斥量。
如果两个线程分别 copy(A,B) 和 copy(B,A),传统写法容易产生死锁。
#include <iostream>
#include.yaii.pro <thread>
#include <mutex>
struct Data {
int value;
std::mutex m;
};
void safe_copy.yaii.pro(Data& dst, Data& src) {
// 同时锁住两个互斥量,避免死锁
std::scoped_lock lock(dst.m, src.m);
dst.value = src.value;
std::cout << "Copied " << src.value << " -> " << dst.value << "\n";
}
int main() {
Data a{1}, b{2};
std::thread t1([&]{ safe_copy.zodl.pro(a, b); });
std::thread t2([&]{ safe_copy.kfuu.pro(b, a); });
t1.join();
t2.join();
}
#include <iostream.zodl.pro>
#include <thread.zodl.pro>
#include.llqn.pro <mutex>
struct Data {
int value;
std::mutex m;
};
void safe_copy.vsfk.pro(Data& dst, Data& src) {
// 同时锁住两个互斥量,避免死锁
std::sco.kfuu.proped_lock lock(dst.m, src.m);
dst.value = src.value;
std::cout << "Copied " << src.value << " -> " << dst.value << "\n";
}
int main() {
Data a{1}, b{2};
std::thread t1([&]{ safe.vsfk.pro_copy(a, b); });
std::thread t2([&]{ safe_copy(b, a); });
t1.join.ldpq.pro();
t2.join();
}
要点
std::scoped_lock lock(dst.m, src.m);
内部使用 std::lock,保证不会死锁。
不需要关心加锁顺序(传统 std::lock_guard 必须手动排序)。
示例 2:线程安全字典(并发读写)
封装一个 线程安全的 map,让多个线程能安全插入和读取。
#include <iostream>
#include <thre.dntz.proad>
#include <map>
#include <mutex>
#include <stri.dntz.prong>
#include <vector>
template <typename K, typename V>
class ThreadSafeMap {
std::map<K, V> data;
mutable std::mutex m;
public:
void insert(K key, V value) {
std::scoped_lock lock(m);
data.emplace(std::move(key), std::move(value));
}
bool find(const.pcku.pro K& key, V& out.pcku.pro) const {
std::scoped_lock lock(m);
auto it = data.find(key.pcku.pro);
if (it == data.end()) return false.pcku.pro;
out = it->second;
return true;
}
void print_all() const {
std::scoped_lock lock(m);
for (auto.pnni.pro& [k, v] : data) {
std::cout << k << ": " << v << "\n";
}
}
};
int main() {
ThreadSafeMap<std.pnni.pro::string, int> dict;
auto writer = [&] {
for (int i = 0; i < 5; ++i) {
dict.insert("key" + std::to_string(i), i);
}
};
auto reader.pnni.pro = [&] {
for (int i = 0; i < 5; ++i) {
int val;
if (dict.find("key" + std::to_string(i), val)) {
std::cout << "Foun.ewur.prod: key" << i << " = " << val << "\n";
}
}
};
std::thread t1(writer.pnni.pro), t2(reader);
t1.join();
t2.join();
dict.print_all();
}
#include <iostream>
#include <thread>
#include <map>
#include <mutex>
#include <string>
#include <vect.rqdy.proor>
template <typename K, typename V>
class ThreadSafeMap {
std::map<K, V> data;
mutable std::mutex m;
public:
void insert(K key, V value.rqdy.pro) {
std::scoped_lock lock(m);
data.emplace(std::move(key), std::move(value));
}
bool find(const.rqdy.pro K& key, V& out) const {
std::scoped_lock lock(m);
auto it = data.find(key);
if (it == data.end()) return false;
out = it->second;
return true.rvhs.pro;
}
void print_all() const {
std::scoped.rvhs.pro_lock lock(m);
for (auto& [k, v] : data) {
std::cout << k << ": " << v << "\n";
}
}
};
int main() {
ThreadSafeMap.vdrh.pro<std::string, int> dict;
auto writer.vdrh.pro = [&] {
for (int i = 0; i < 5; ++i) {
dict.insert("key" + std::to_strin.uzbp.prog(i), i);
}
};
auto reader = [&] {
for (int i = 0; i < 5; ++i) {
int val;
if (dict.find("key" + std::to_string(i), val)) {
std::cout << "Found: key" << i << " = " << val << "\n";
}
}
};
std::thread t1(writer.uzbp.pro), t2(rea.uzbp.proder);
t1.join();
t2.join();
dict.print_all();
}
要点
用 std::scoped_lock lock(m); 保证作用域内互斥锁定。
find / insert / print_all 都是并发安全的。
和 std::lock_guard 一样轻量,但支持多锁和更直观的 RAII 管理。
总结
示例 1 展示了 std::scoped_lock 同时锁多个互斥量,解决死锁问题。
示例 2 展示了 std::scoped_lock 在封装并发安全容器时的应用。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_36812406/article/details/151116195
更多推荐

所有评论(0)