跳转到内容

C++ 互斥量

来自代码酷


C++互斥量(Mutex)是C++标准库中用于实现多线程同步的核心机制之一,用于保护共享数据免受并发访问导致的竞态条件(Race Condition)问题。本文将从基础概念到高级用法全面讲解互斥量的使用。

概述[编辑 | 编辑源代码]

互斥量(Mutual Exclusion)是一种同步原语,它确保同一时间只有一个线程可以访问共享资源。在C++中,互斥量通过std::mutex类实现,通常与std::lock_guardstd::unique_lock配合使用。

为什么需要互斥量?[编辑 | 编辑源代码]

当多个线程同时读写共享数据时,可能导致数据不一致或程序崩溃。例如:

  • 线程A读取变量x=5,准备执行x=x+1
  • 线程B抢先修改x=10
  • 线程A继续执行,最终得到x=6(而非预期的11)

互斥量通过强制串行化访问解决此类问题。

基本用法[编辑 | 编辑源代码]

std::mutex[编辑 | 编辑源代码]

最基础的互斥量类型,提供lock()unlock()try_lock()方法。

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

std::mutex mtx;
int shared_data = 0;

void increment() {
    mtx.lock();
    ++shared_data;
    mtx.unlock();
}

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

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

    std::cout << "Final value: " << shared_data << std::endl;
    // 输出: Final value: 2
}

自动锁管理[编辑 | 编辑源代码]

直接使用lock()/unlock()容易因异常导致死锁。推荐使用RAII包装器:

std::lock_guard[编辑 | 编辑源代码]

void safe_increment() {
    std::lock_guard<std::mutex> lock(mtx);
    ++shared_data; // 离开作用域自动解锁
}

std::unique_lock[编辑 | 编辑源代码]

提供更灵活的控制(如延迟锁定、手动解锁):

void flexible_increment() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // ...其他不需要同步的操作...
    lock.lock();
    ++shared_data;
    // 可提前解锁
    lock.unlock();
}

高级特性[编辑 | 编辑源代码]

递归互斥量[编辑 | 编辑源代码]

std::recursive_mutex允许同一线程多次加锁:

std::recursive_mutex rmtx;

void recursive_function(int n) {
    std::lock_guard<std::recursive_mutex> lock(rmtx);
    if (n > 0) {
        recursive_function(n - 1);
    }
}

定时互斥量[编辑 | 编辑源代码]

std::timed_mutex提供带超时的锁定尝试:

std::timed_mutex tmtx;

void timed_access() {
    if (tmtx.try_lock_for(std::chrono::milliseconds(100))) {
        // 成功获取锁
        tmtx.unlock();
    }
}

死锁预防[编辑 | 编辑源代码]

多个互斥量可能引发死锁。解决方案: 1. 按固定顺序上锁 2. 使用std::lock()同时锁定多个互斥量:

std::mutex mtx1, mtx2;

void safe_multilock() {
    std::lock(mtx1, mtx2); // 原子性地锁定两个互斥量
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
    // 临界区代码
}

性能考虑[编辑 | 编辑源代码]

互斥量操作通常涉及系统调用,开销较大。优化策略:

  • 减小临界区范围
  • 考虑无锁编程(仅适用于特定场景)
  • 使用读写锁(std::shared_mutex)区分读写操作

实际案例[编辑 | 编辑源代码]

线程安全队列[编辑 | 编辑源代码]

classDiagram class ThreadSafeQueue { -std::queue<T> data_queue -std::mutex mtx +void push(T item) +bool try_pop(T& value) }

template<typename T>
class ThreadSafeQueue {
    std::queue<T> data_queue;
    std::mutex mtx;
public:
    void push(T item) {
        std::lock_guard<std::mutex> lock(mtx);
        data_queue.push(item);
    }

    bool try_pop(T& value) {
        std::lock_guard<std::mutex> lock(mtx);
        if (data_queue.empty()) return false;
        value = data_queue.front();
        data_queue.pop();
        return true;
    }
};

数学原理[编辑 | 编辑源代码]

互斥量实现的底层通常依赖原子操作和内存屏障。基本锁定算法可以用以下伪代码表示:

解析失败 (未知函数“\begin{aligned}”): {\displaystyle \begin{aligned} &\text{while not atomic_compare_exchange(lock, 0, 1)} \\ &\quad \text{wait or yield} \\ &\text{memory_barrier()} \end{aligned} }

常见问题[编辑 | 编辑源代码]

Q: 为什么有时用mutex仍出现数据竞争?[编辑 | 编辑源代码]

A: 可能原因: 1. 未保护所有共享数据访问路径 2. 错误地复制了mutex对象(mutex不可复制/移动)

Q: mutex和atomic的区别?[编辑 | 编辑源代码]

A: atomic适用于简单数据类型(如int、bool)的原子操作,mutex适用于保护复杂操作或代码块。

最佳实践[编辑 | 编辑源代码]

1. 优先使用lock_guard而非手动lock()/unlock() 2. 为每个共享资源使用单独的mutex 3. 避免在持有锁时调用用户代码(可能引发死锁) 4. 使用工具如ThreadSanitizer检测数据竞争

扩展阅读[编辑 | 编辑源代码]

  • C++17引入的std::scoped_lock(多mutex的增强版lock_guard)
  • 条件变量(std::condition_variable)与mutex的配合使用
  • 无锁数据结构设计