C++ 并发编程:深入理解 std::mutex 和 std::lock_guard

在这里插入图片描述

C++ 并发编程:深入理解 std::mutexstd::lock_guard

一、为什么需要互斥锁?

在多线程程序中,当多个线程同时访问共享资源时,可能会引发数据竞争(Data Race)问题。比如两个线程同时修改同一个全局变量:

int counter = 0; // 共享资源

void increment() {
    for (int i = 0; i < 100000; ++i) {
        counter++; // 非原子操作
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join(); t2.join();
    std::cout << "Final counter: " << counter; // 可能输出小于200000

运行结果可能是不可预测的,因为 counter++ 不是原子操作。此时需要互斥锁(Mutex)来保证操作的原子性。


二、std::mutex 基本用法

std::mutex 是 C++11 提供的互斥锁类,通过 lock()unlock() 方法手动控制锁。

修正后的线程安全代码:

include <mutex>

std::mutex mtx;
int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        mtx.lock();
        counter++;
        mtx.unlock(); // 必须显式解锁
}
// 最终输出一定是200000

手动锁的痛点:

  • 忘记解锁会导致死锁。
  • 异常发生时可能跳过解锁步骤。

三、std::lock_guard:自动锁管理

std::lock_guard 是一个 RAII 包装类,构造时自动加锁,析构时自动解锁,确保异常安全。

使用示例:

void increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx); // 自动加锁
        counter++;
        // 离开作用域时自动解锁
}

核心特性:

  1. 自动管理生命周期:无需手动调用 unlock()
  2. 异常安全:即使代码抛出异常,锁也会被释放。
  3. 不可复制:确保锁的所有权唯一。

四、lock_guard 的底层原理

简化实现代码:

template <typename Mutex>
class lock_guard {
public:
    explicit lock_guard(Mutex& m) : mutex(m) {
        mutex.lock(); // 构造时加锁
~lock_guard() {

        mutex.unlock(); // 析构时解锁
// 禁止复制

    lock_guard(const lock_guard&) = delete;
    lock_guard& operator=(const lock_guard&) = delete;

private:
    Mutex& mutex;
};

五、实际应用场景

案例1:线程安全的银行账户

class BankAccount {
private:
    std::mutex mtx;
    double balance;
public:
    void deposit(double amount) {
        std::lock_guard<std::mutex> lock(mtx);
        balance += amount;
void withdraw(double amount) {

        std::lock_guard<std::mutex> lock(mtx);
        if (balance >= amount) {
            balance -= amount;
}

};

案例2:保护文件写入

std::mutex log_mutex;

void write_log(const std::string& message) {
    std::lock_guard<std::mutex> lock(log_mutex);
    std::ofstream file("app.log", std::ios::app);
    file << message << std::endl;
    // 文件流和锁都会自动释放

六、注意事项

  1. 锁的粒度:
// 错误:锁范围过大,影响性能
std::lock_guard<std::mutex> lock(mtx);
{
       data1.process();
       data2.process(); // 这两个操作可能不需要共享锁
}
// 正确:细化锁范围
{
	   std::lock_guard<std::mutex> lock1(mtx1);
       data1.process();
}
{
       std::lock_guard<std::mutex> lock2(mtx2);
       data2.process();
}

  1. 避免嵌套死锁:
std::mutex mtx;

   void foo() {
       std::lock_guard<std::mutex> lock(mtx);
       bar(); // 如果bar()也申请同一个锁,导致死锁
}
void bar() {

       std::lock_guard<std::mutex> lock(mtx); // 死锁!
}

  1. 配合其他锁类型:
  • std::unique_lock:更灵活(延迟锁定、转移所有权)。
  • std::shared_mutex:读写分离锁(C++17)。

七、常见问题解答

Q1:lock_guard 和 unique_lock 有什么区别?

特性lock_guardunique_lock
锁策略必须立即锁定可延迟锁定 (defer_lock)
所有权转移不支持支持
性能更高(无额外开销)稍低(更多功能)
适用场景简单作用域锁定需要灵活控制锁的情况

Q2:如何选择锁的类型?

  • 简单场景用 lock_guard(90% 的情况)。
  • 需要条件变量或延迟锁定用 unique_lock。

八、总结

  • std::mutex:基础互斥锁,需手动管理锁生命周期。
  • std::lock_guard:
    • 自动管理锁,确保异常安全。
    • 适用于简单的作用域锁定。
    • 代码更简洁,避免忘记解锁。

最佳实践:

{
	std::lock_guard<std::mutex> lock(mtx); // 进入作用域加锁
	// 操作共享资源...
}	// 离开作用域自动解锁

参考

【1】C++学习随笔——lock_guard和mutex的使用方法
【2】std::lock_guard

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智驾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值