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

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐