<mutex>
是 C++ 标准库中用于线程同步的核心头文件,提供了多种互斥量类型及其管理工具。利用这些工具,你可以保护共享数据,防止多个线程同时访问同一资源时引发数据竞争或不一致问题。下面将介绍 <mutex>
的主要组件和常见使用方式。
目录
<mutex>
头文件简介- 互斥量基础
- 2.1 std::mutex
- 2.2 std::recursive_mutex
- 2.3 定时互斥量
- RAII 风格的锁管理
- 3.1 std::lock_guard
- 3.2 std::unique_lock
- 多锁与死锁预防
- 常见注意事项
- 结论
1. <mutex>
头文件简介
C++ 的 <mutex>
提供了线程同步所需的各种互斥量(mutex)和相关工具。通过这些工具,可以防止多个线程同时操作共享资源,从而保证程序的正确性和数据一致性。要使用这些功能,只需在程序中包含该头文件:
#include <mutex>
2. 互斥量基础
2.1 std::mutex
- 功能:
std::mutex
是最基本的互斥量,用于保护临界区,确保同一时刻只有一个线程访问共享数据。 - 常用方法:
lock()
:上锁,如果互斥量已被锁住,则当前线程阻塞。unlock()
:解锁,释放互斥量。try_lock()
:尝试上锁,如果无法立即获得锁则返回false
,否则返回true
。
示例:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
int counter = 0;
void increment() {
mtx.lock();
++counter;
std::cout << "Counter: " << counter << std::endl;
mtx.unlock();
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
return 0;
}
2.2 std::recursive_mutex
- 功能:允许同一线程对同一互斥量进行多次加锁而不会引发死锁,适用于递归调用或嵌套调用场景。
- 使用注意:每次调用
lock()
后必须对应一次unlock()
。
示例:
#include <iostream>
#include <mutex>
#include <thread>
std::recursive_mutex rec_mtx;
int value = 0;
void recursiveFunction(int n) {
if(n <= 0)
return;
rec_mtx.lock();
++value;
std::cout << "Value: " << value << " at recursion level: " << n << std::endl;
recursiveFunction(n - 1);
rec_mtx.unlock();
}
int main() {
std::thread t(recursiveFunction, 3);
t.join();
return 0;
}
2.3 定时互斥量
- 功能:例如
std::timed_mutex
和std::recursive_timed_mutex
提供带超时功能的加锁接口,如try_lock_for
和try_lock_until
,允许在指定时间内尝试加锁。
3. RAII 风格的锁管理
直接调用 lock()
和 unlock()
虽然简单,但容易因为异常或遗漏解锁而导致死锁。C++ 提供了 RAII(资源获取即初始化)风格的锁管理类,它们能自动在离开作用域时释放锁,从而大大提高代码安全性。
3.1 std::lock_guard
- 特点:构造时自动上锁,析构时自动解锁。适用于简单场景。
示例:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
int counter = 0;
void safeIncrement() {
std::lock_guard<std::mutex> lock(mtx);
++counter;
std::cout << "Safe counter: " << counter << std::endl;
}
int main() {
std::thread t1(safeIncrement);
std::thread t2(safeIncrement);
t1.join();
t2.join();
return 0;
}
3.2 std::unique_lock
- 特点:比
std::lock_guard
更灵活,支持延迟加锁、提前解锁和条件变量协作等功能。
示例:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
int counter = 0;
void flexibleIncrement() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// 可以在这里执行其他操作,再决定何时加锁
lock.lock();
++counter;
std::cout << "Flexible counter: " << counter << std::endl;
// 可选:lock.unlock(); 提前释放锁
}
int main() {
std::thread t1(flexibleIncrement);
std::thread t2(flexibleIncrement);
t1.join();
t2.join();
return 0;
}
4. 多锁与死锁预防
在涉及多个共享资源的情况下,多个线程同时请求多个互斥量可能会导致死锁。为避免此类问题,你可以:
- 一致性加锁顺序:所有线程在获取多个锁时,按照相同的顺序获取。
- 使用 std::lock():该函数可以一次性锁定多个互斥量,并避免死锁。
示例:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx1, mtx2;
void taskA() {
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::cout << "Task A completed." << std::endl;
}
void taskB() {
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::cout << "Task B completed." << std::endl;
}
int main() {
std::thread t1(taskA);
std::thread t2(taskB);
t1.join();
t2.join();
return 0;
}
5. 常见注意事项
- 确保总是解锁:尽量使用 RAII 风格的锁类(如
std::lock_guard
和std::unique_lock
)自动管理锁的生命周期。 - 缩小临界区:将持锁的代码范围最小化,避免在锁内执行耗时操作。
- 选择合适的互斥量:根据需求选择标准互斥量、递归互斥量或定时互斥量,以平衡安全性和性能。
6. 结论
<mutex>
为 C++ 多线程编程提供了重要的同步机制。通过合理使用互斥量和 RAII 风格的锁管理工具,你可以防止数据竞争、减少死锁风险,从而编写出线程安全且高效的并发程序。掌握这些工具是编写可靠多线程代码的关键。
推荐阅读:
发表回复