C++多线程编程lock_guard和unique_lock

前言

今天分享一下经常用于C++多线程编程中管理互斥量(mutex)的两种方法,以及两者的区别。

std::lock_guard 和 std::unique_lock 都是用于管理互斥量(mutex)的类,都是 C++ 标准库提供的互斥锁 RAII 封装工具,用于实现互斥访问。两者具体的区别和使用如下:

std::lock_guard


1. 不支持手动解锁: 锁的释放只能在 std::lock_guard 的析构函数中发生,无法手动解锁。只能在其作用域内自动地加锁和解锁互斥量,也可以使用{}来控制轻锁lock_guard的生命周期,使用{}限制生命周期区域在大型项目中经常使用,不仅仅是lock_guard可以用,这里就不详细解释了。这个特点同时也这有助于防止意外异常终止导致的死锁。

2. 不能拷贝、赋值、移动: std::lock_guard 不允许被拷贝、复制或移动,因为这可能导致不一致的锁状态。

3. 简单性: 适合用于简单的、不可中断的临界区场景。

示例代码:

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

std::mutex mtx;

void print_block(int n) {
    std::lock_guard<std::mutex> lock(mtx);
    // 临界区
    std::cout << "Thread " << n << " is in the critical section\n";
}

int main() {
    std::thread t1(print_block, 1);
    std::thread t2(print_block, 2);

    t1.join();
    t2.join();

    return 0;
}

std::unique_lock

1. 可移动但不可复制: std::unique_lock 允许通过移动语义转移锁的所有权,但不能被复制。
2. 灵活性: 可以在构造时延迟锁定,或者在作用域内手动解锁和重新锁定。
3. 支持条件变量: 适合与条件变量(std::condition_variable)一起使用。

示例代码:

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

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_block(int n) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; }); // 等待条件变量

    // 临界区
    std::cout << "Thread " << n << " is in the critical section\n";
}

int main() {
    std::thread t1(print_block, 1);
    std::thread t2(print_block, 2);

    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟其他工作
    {
        std::unique_lock<std::mutex> lock(mtx);
        ready = true;  // 修改共享状态
        cv.notify_all(); // 通知所有等待的线程
    }

    t1.join();
    t2.join();

    return 0;
}

需要注意的是,如果确定使用unique_lock了,就不要再使用 mutex 的 lock 和 unlock ,直接使用 unique_lock 的 lock 和 unlock,混合使用会导致程序异常,原因是unique_lock 内部会维护一个标识用来记录自己管理的 锁 当前处于何种状态,如果直接使用 mutex的成员函数,unique_lock无法更新自己的状态,从而导致 double lock 和 double unlock(因为unique_lock一定会在析构的时候unlock),这两种情况都会导致崩溃,下面我也会给一个不用lock_guard和unique_lock的示例。

两者的区别

其实就是将上述它们各自的特点概述一下就是区别:

1. std::lock_guard一旦锁定,不能解锁,也不能重新锁定,只能在 std::lock_guard 的析构函数中释放,而std::unique_lock可以在锁定后解锁,并且可以重新锁定。

2. std::lock_guard 不允许被拷贝、复制或移动,而std::unique_lock可移动但不可复制。

3. lock_guard 功能单一简单,只能用作自释放锁,而unique_lock 提供了更多的控制锁的行为,比如锁超时、不锁定、条件变量等,当然付出的代价就是unique_lock开销更大,会慢一点。

使用场景

具体使用哪个,需要具体需求和互斥锁的使用场景来判断:
如果你需要更简单的锁定机制,lock_guard 是一个好选择。如果你需要更复杂的锁管理,unique_lock 提供了更多的控制选项。unique_lock 提供了更多的控制锁的行为,比如锁超时、不锁定、条件变量等。

总结

其实绕来绕去本质上还是 RAII 思想,上述两个代码是我写的两个简单的实例,仅供参考,其实关于锁定的互斥量的使用,记住一点就可以,C++必须要贯彻RAII 思想去写代码,下面是不用lock_guard和unique_lock的示例,如下:

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

class LockGuard {
public:
    explicit LockGuard(std::mutex &mtx) : mutex_(mtx) {
        mutex_.lock();
    }

    ~LockGuard() {
        mutex_.unlock();
    }

    // 禁止复制
    LockGuard(const LockGuard &) = delete;
    LockGuard &operator=(const LockGuard &) = delete;

private:
    std::mutex &mutex_;
};

// 互斥量
std::mutex mtx;
// 多线程操作的变量
int shared_data = 0;

void increment() {
    for (int i = 0; i < 10000; ++i) {
        // 申请锁
        LockGuard lock(mtx);
        ++shared_data;
        // 作用域结束后会析构 然后释放锁
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Shared data: " << shared_data << std::endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值