C++ 并发编程指南(7)使用std::lock_guard与std::unique_lock管理锁

本文详细介绍了C++中std::lock_guard和std::unique_lock在多线程编程中的管理和使用,包括它们的原理、特点、示例和应用场景,重点强调了两者在互斥锁管理上的区别和灵活性。
摘要由CSDN通过智能技术生成

前言

在C++的多线程编程中,互斥锁的管理是确保数据一致性和线程同步的关键。std::unique_lock和std::lock_guard是两种用于管理互斥锁的智能锁对象,它们提供了便捷且安全的方式来处理并发访问共享资源的问题。下面我们来详细探讨这两种机制。

一、使用std::lock_guard与std::unique_lock管理锁

1、std::lock_guard

lock_guard 的实现原理主要基于 RAII(Resource Acquisition Is Initialization)原则,这是 C++ 中的一个重要编程技巧。RAII
意味着资源的获取(如内存分配、文件打开、互斥锁锁定等)与对象的初始化绑定在一起,而资源的释放(如内存释放、文件关闭、互斥锁解锁等)则与对象的析构绑定在一起。lock_guard 的实现通常包括以下几个部分:

  • 构造函数lock_guard 的构造函数接受一个互斥锁(或其他类型的锁)作为参数,并在构造函数内部调用该互斥锁的 lock() 方法来锁定互斥锁。这样,当 lock_guard 对象被创建时,互斥锁就被自动锁定了。
  • 析构函数lock_guard 的析构函数负责在对象生命周期结束时自动调用互斥锁的 unlock() 方法来解锁互斥锁。由于析构函数在对象离开其作用域时自动被调用,因此这确保了互斥锁在不再需要时会被正确地解锁,即使在异常情况下也是如此。
  • 禁止复制和赋值lock_guard 的实现通常会禁止对象的复制和赋值操作,这是为了避免出现多个 lock_guard 对象持有同一个互斥锁的情况,从而防止死锁的发生。

下面是一个简化的 lock_guard 实现示例:

template <typename Mutex>
class lock_guard {
public:
    explicit lock_guard(Mutex& mtx) : mutex_(mtx) {
        mutex_.lock(); // 在构造函数中锁定互斥锁
    }

    ~lock_guard() {
        mutex_.unlock(); // 在析构函数中解锁互斥锁
    }

    // 禁止复制和赋值
    lock_guard(const lock_guard&) = delete;
    lock_guard& operator=(const lock_guard&) = delete;

private:
    Mutex& mutex_; // 持有对互斥锁的引用
};

在这个示例中,lock_guard 是一个模板类,它可以接受不同类型的互斥锁作为参数。在构造函数中,它通过调用mutex_.lock() 来锁定互斥锁。当 lock_guard对象离开其作用域时,析构函数会被自动调用,从而解锁互斥锁。此外,复制构造函数和赋值运算符被显式删除,以防止 lock_guard对象的复制和赋值。

使用 lock_guard 可以极大地简化多线程编程中的互斥锁管理,同时确保在异常安全的情况下正确地解锁互斥锁。

2、std::unique_lock

unique_lock 是 C++11 标准库中提供的一个类模板,用于封装和管理互斥量(mutex),以实现多线程同步。unique_lock 的实现原理相较于 lock_guard 更为复杂和灵活,因为它提供了更多的控制选项和功能。它不仅允许在运行时锁定和解锁互斥锁,还可以配合条件变量使用,以实现线程间的精确同步。unique_lock 的实现通常包含以下几个关键方面:

  • 互斥量的所有权unique_lock 对象在构造时可以接收一个互斥量的引用,并可以选择立即锁定该互斥量。一旦 unique_lock 对象拥有了互斥量的所有权,它就可以控制互斥量的锁定和解锁状态。

  • 灵活的锁定策略unique_lock 提供了多种锁定策略,可以通过构造函数或成员函数来指定。例如,可以使用 std::defer_lock 策略来延迟锁定互斥量,或者使用 std::try_to_lock 策略来尝试锁定互斥量(如果互斥量已经被其他线程锁定,则立即返回)。

  • 条件变量的支持unique_lock 可以与条件变量(std::condition_variable)一起使用,以实现线程间的同步和通信。通过 unique_lock 对象,线程可以等待条件变量触发,或者通知其他线程条件已经满足。

  • 所有权的转移unique_lock 允许在构造时或在任意时刻通过移动操作(move operation)将互斥量的所有权转移给其他 unique_lock 对象。这种特性使得 unique_lock 在处理复杂的多线程逻辑时更加灵活。

  • 析构函数的行为unique_lock 的析构函数会自动解锁互斥量(如果当前对象拥有互斥量的所有权)。这种自动解锁的行为保证了即使在异常情况下,互斥量也能被正确释放。

下面是一个简化的 unique_lock 实现示例:

template <typename Mutex>
class unique_lock {
public:
    // 构造函数,可以选择立即锁定互斥量
    explicit unique_lock(Mutex& mtx, std::defer_lock_t) : mutex_(mtx), owns_lock_(false) {}
    unique_lock(Mutex& mtx, std::try_to_lock_t) : mutex_(mtx), owns_lock_(try_lock(mtx)) {}
    explicit unique_lock(Mutex& mtx) : mutex_(mtx), owns_lock_(true) { lock(mtx); }

    // 解锁互斥量(如果当前对象拥有互斥量的所有权)
    void unlock() {
        if (owns_lock_) {
            mutex_.unlock();
            owns_lock_ = false;
        }
    }

    // 析构函数,自动解锁互斥量(如果当前对象拥有互斥量的所有权)
    ~unique_lock() {
        unlock();
    }

    // 移动构造函数,转移互斥量的所有权
    unique_lock(unique_lock&& other) noexcept : mutex_(other.mutex_), owns_lock_(other.owns_lock_) {
        other.owns_lock_ = false;
    }

    // 其他成员函数和成员变量...

private:
    Mutex& mutex_;        // 持有对互斥量的引用
    bool owns_lock_;      // 指示当前对象是否拥有互斥量的所有权

    // 实现锁定和尝试锁定的辅助函数...
};

在这个示例中,unique_lock 是一个模板类,它可以接受不同类型的互斥量作为参数。它使用了一个布尔变量 owns_lock_来跟踪是否拥有互斥量的所有权。构造函数根据不同的参数类型(如std::defer_lock_tstd::try_to_lock_t或无参数)来决定是否立即锁定互斥量。析构函数会自动解锁互斥量(如果当前对象拥有所有权)。此外,unique_lock还提供了移动构造函数,以支持所有权的转移。

unique_lock 的灵活性和功能使其在多线程编程中非常有用,特别是在需要更复杂的同步逻辑和条件变量使用时。然而,由于它的复杂性,相比 lock_guardunique_lock 的性能可能会稍低一些。因此,在选择使用 lock_guard 还是 unique_lock 时,应根据具体的需求和场景来权衡。

3、std::unique_lock与std::condition_variable配合使用

std::unique_lockstd::condition_variable配合使用是多线程编程中实现线程间同步和通信的强大机制。条件变量允许一个或多个线程等待直到满足特定条件,而unique_lock提供了必要的锁机制来安全地检查这些条件并管理线程的睡眠和唤醒。以下是std::unique_lockstd::condition_variable配合使用的基本步骤:

  • 创建一个互斥锁 std::mutex 和一个条件变量 std::condition_variable
  • 在需要等待条件的线程中使用 std::unique_lock 锁定互斥锁。
  • 线程调用条件变量的 wait() 方法,传入 unique_lock 对象,以及一个可选的lambda表达式(谓词)来检查等待的条件是否已满足。如果条件不满足,wait() 方法将释放锁并使线程进入睡眠状态。
  • 当其他线程修改了共享资源并满足了条件后,它们会通知条件变量,这通常通过调用 notify_one()notify_all() 方法来完成。
  • 当条件变量收到通知后,等待的线程将被唤醒,重新锁定互斥锁,并再次检查条件是否确实已经满足(这个过程称为“条件变量检查”)。
  • 如果条件满足,线程继续执行;否则,它将再次调用 wait() 方法进入等待状态。

以下是一个示例代码,展示了如何结合使用 std::unique_lockstd::condition_variable

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false; // 共享资源,表示某个条件是否已满足

void workerThread() {
    std::unique_lock<std::mutex> lck(mtx);
    while (!ready) { // 等待直到条件满足
        cv.wait(lck, []{ return ready; }); // 在这里线程可能被阻塞
    }
    // 继续处理其他任务...
}

int main() {
    std::thread t(workerThread);
    // ... 在其他线程中进行一些操作,最终设置 ready = true
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true; // 修改共享资源
    }
    cv.notify_one(); // 通知等待的线程
    t.join();
    return 0;
}

在上面的示例中,工作线程会等待 ready 变量变为 true。一旦主线程设置了 ready 并调用notify_one(),工作线程就会从 wait() 调用中返回,然后检查条件,并在确认条件已满足后继续执行。这种配合使用的方式确保了线程在条件不满足时不会占用不必要的CPU周期,同时在条件满足时能及时醒来进行处理。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
shared_lock<std::shared_mutex>是C++中用于管理std::shared_mutex的共享的类。它可以在多个线程中同时获得对共享资源的读取访问权。\[1\]在使用shared_lock<std::shared_mutex>时,通常会将其与std::shared_mutex一起使用,以实现对共享资源的并发读取操作。\[1\]例如,在dns_cache类的find_entry函数中,使用shared_lock<std::shared_mutex>来获取对entries的共享访问权,以允许多个线程同时读取entries中的数据。\[2\]这样可以提高并发性能,因为多个线程可以同时读取共享资源而无需互斥定。 #### 引用[.reference_title] - *1* *3* [C++互斥对象std::mutex与std::shared_mutex;互斥std::lock_guardstd::unique_lockstd::shared_...](https://blog.csdn.net/qq_33726635/article/details/109693403)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [c++std::unique_lock, std::lock, std::scoped_lockstd::condition_variable](https://blog.csdn.net/weixin_44537992/article/details/122734223)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值