std::mutex
std::mutex是 C++ 标准库(自 C++11 起引入)中用于多线程编程的核心组件,位于<mutex>头文件中。它代表互斥量(Mutual Exclusion),用于保护共享数据,防止多个线程同时访问导致的数据竞争(Data Race)。
以下是关于std::mutex的核心用法、最佳实践及注意事项:
1. 基本用法
std::mutex提供了两个主要成员函数:
lock(): 尝试获取锁。如果锁已被其他线程持有,当前线程会阻塞直到锁被释放。unlock(): 释放锁。
原始用法示例(不推荐直接使用,见下文“最佳实践”):
#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 << "Result: " << shared_data << std::endl; return 0; }2. 最佳实践:使用 RAII 锁守卫
手动调用lock()和unlock()容易出错(例如在锁持有期间发生异常导致unlock()未被调用,造成死锁)。强烈建议使用 RAII(资源获取即初始化)机制的锁守卫类:
std::lock_guard(C++11)
最简单的锁守卫。构造时加锁,析构时自动解锁。适用于整个作用域都需要锁的情况。
#include <mutex> std::mutex mtx; void safe_increment() { std::lock_guard<std::mutex> lock(mtx); // 构造时加锁 // 临界区代码 // 函数退出(无论正常返回还是抛出异常)时,lock 析构,自动解锁 }std::unique_lock(C++11)
比lock_guard更灵活。支持延迟锁定、手动解锁、尝试锁定以及配合条件变量 (std::condition_variable) 使用。
#include <mutex> std::mutex mtx; void flexible_increment() { std::unique_lock<std::mutex> lock(mtx); // ... 做一些不需要锁的操作 ... lock.unlock(); // 可以手动提前解锁 // ... 做一些不需要锁的操作 ... lock.lock(); // 也可以重新加锁 }3. 重要特性与限制
- 不可复制/移动:
std::mutex对象本身不能被复制或移动。这意味着包含std::mutex的类也不能默认生成拷贝构造函数或拷贝赋值运算符。 - 非递归性:
std::mutex是非递归的。如果一个已经持有该锁的线程再次尝试lock()同一个 mutex,会导致死锁(Deadlock)。- 解决方案: 如果需要递归锁,请使用
std::recursive_mutex。
- 解决方案: 如果需要递归锁,请使用
- 所有权:
std::mutex不记录拥有者线程 ID。虽然通常由加锁的线程解锁,但标准并未强制检查(不过在不同线程间传递锁通常逻辑上是错误的)。
4. 常见变体
| 类型 | 描述 | 适用场景 |
|---|---|---|
std::mutex | 标准互斥量,独占访问。 | 绝大多数临界区保护。 |
std::recursive_mutex | 可递归互斥量。同一线程可多次加锁。 | 必须在同一线程内递归调用受保护函数时(尽量避免设计成这样)。 |
std::timed_mutex | 带超时的互斥量。支持try_lock_for和try_lock_until。 | 需要避免无限等待,尝试获取锁失败后执行其他逻辑时。 |
std::recursive_timed_mutex | 可递归且带超时的互斥量。 | 上述两者的结合。 |
std::shared_mutex(C++17) | 共享互斥量(读写锁)。允许多个读者或一个写者。 | 读多写少的场景,提高并发读取性能。 |
5. 典型错误示例
死锁示例(自锁):
void bad_function(std::mutex& m) { std::lock_guard<std::mutex> lock1(m); // 以下代码会导致死锁,因为当前线程已经持有了 m std::lock_guard<std::mutex> lock2(m); }异常安全示例(展示为什么需要 lock_guard):
void risky_function(std::mutex& m) { m.lock(); if (some_condition) { throw std::runtime_error("Error!"); // 如果没有 lock_guard,m.unlock() 永远不会被执行,锁泄露 } m.unlock(); }使用std::lock_guard则无需担心此问题。
总结
在现代 C++ 开发中:
- 始终包含
<mutex>。 - 优先使用
std::lock_guard管理std::mutex。 - 仅在需要配合条件变量或需要精细控制锁生命周期时使用
std::unique_lock。 - 尽量避免手动调用
lock()和unlock()。
