1.什么是std::mutex
std::mutex 是C++11引入的标准库头文件中定义的一个类,代表“互斥锁”(Mutual Exclusion)。用于保护共享数据免受多线程同时访问的主要同步原语。
核心思想:互斥锁就像一个小房间(临界区)的钥匙。一次只有一个线程可以持有这把钥匙(锁)。当线程持有钥匙时,它可以进入房间访问共享数据。其他线程必须等待,直到钥匙被放回(解锁),然后它们才能竞争去拿到这把钥匙。
2.为什么需要std::mutex ?
在多线程环境中,如果多个线程不加控制地同时读写同一块共享数据,会导致数据竞争(Data Race)。数据竞争的后果是未定义行为(Undefined Behavior),可能导致程序崩溃、计算错误、数据损坏等难以预料的后果。
std::mutex 的作用就是消除数据竞争,确保任何时候最多只有一个线程可以执行被保护的代码段(临界区),从而保证数据操作的原子性和正确性。
3.核心成员函数
重要特性:
std::mutex既不可复制也不可移动。同一个线程不允许对已经锁定的
std::mutex再次调用lock()或try_lock(),这会导致未定义行为(通常是死锁)。这种锁称为非递归锁。
4.基本使用方式及其风险
#include <iostream>
#include <thread>
#include <mutex>
std::mutex g_mutex; // 全局互斥锁
int shared_data = 0; // 共享数据
void increment() {
for (int i = 0; i < 100000; ++i) {
g_mutex.lock(); // 进入临界区前加锁
++shared_data; // 临界区:操作共享数据
g_mutex.unlock(); // 离开临界区后解锁
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final value: " << shared_data << std::endl; // 总是 200000
return 0;
}风险:上述直接使用 lock() 和 unlock() 的方式是不安全的。如果在临界区中发生了异常、或者程序员忘记调用 unlock(),锁将永远不会被释放,导致所有等待该锁的线程永久阻塞(死锁)。
5.更安全的方式:使用 RAII 管理锁
为了解决上述问题,C++ 标准库提供了RAII(Resource Acquisition Is Initialization)风格的包装器类,来自动管理锁的生命周期。
a.std::lock_guard
std::lock_guard 是一个轻量级的 RAII 包装器。它在构造时自动锁定互斥量,在析构时(例如离开作用域时)自动解锁。即使中间发生异常,也能保证锁被释放。
适用场景:简单的临界区,整个作用域都需要加锁。99% 的情况都应该优先使用它。
void safe_increment() {
for (int i = 0; i < 100000; ++i) {
// 构造函数中调用 g_mutex.lock()
std::lock_guard<std::mutex> lock(g_mutex);
++shared_data;
// lock 的析构函数在作用域结束时自动调用 g_mutex.unlock()
}
}b.std::unique_lock()
std::unique_lock 比 std::lock_guard 更灵活,但开销稍大。它提供了以下额外功能:
延迟加锁:构造时可以指定
std::defer_lock,不立即加锁,之后再手动调用lock()。提前解锁:可以手动调用
unlock()在作用域结束前释放锁,减少锁的持有时间。所有权转移:
std::unique_lock是可移动的,但不可复制。可以与条件变量
std::condition_variable一起使用(这是必须的)。
void flexible_increment() {
for (int i = 0; i < 100000; ++i) {
std::unique_lock<std::mutex> ulock(g_mutex, std::defer_lock);
// ... 这里可以执行一些不需要锁的操作 ...
ulock.lock(); // 现在需要操作共享数据了,手动加锁
++shared_data;
ulock.unlock(); // 可以提前解锁
// ... 这里又可以执行一些不需要锁的操作 ...
// ulock 析构时,如果还持有锁,会自动解锁
}
}适用场景:需要更灵活锁管理的复杂情况,或需要与条件变量配合时。
6.其他类型的互斥量
标准库还提供了其他几种互斥量以适应不同场景: