前言
今天分享一下经常用于C++多线程编程中管理互斥量(mutex)的两种方法,以及两者的区别。
std::lock_guard 和 std::unique_lock 都是用于管理互斥量(mutex)的类,都是 C++ 标准库提供的互斥锁 RAII 封装工具,用于实现互斥访问。两者具体的区别和使用如下:
std::lock_guard
1. 不支持手动解锁: 锁的释放只能在 std::lock_guard 的析构函数中发生,无法手动解锁。只能在其作用域内自动地加锁和解锁互斥量,也可以使用{}来控制轻锁lock_guard的生命周期,使用{}限制生命周期区域在大型项目中经常使用,不仅仅是lock_guard可以用,这里就不详细解释了。这个特点同时也这有助于防止意外异常终止导致的死锁。
2. 不能拷贝、赋值、移动: std::lock_guard 不允许被拷贝、复制或移动,因为这可能导致不一致的锁状态。
3. 简单性: 适合用于简单的、不可中断的临界区场景。
示例代码:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
void print_block(int n) {
std::lock_guard<std::mutex> lock(mtx);
// 临界区
std::cout << "Thread " << n << " is in the critical section\n";
}
int main() {
std::thread t1(print_block, 1);
std::thread t2(print_block, 2);
t1.join();
t2.join();
return 0;
}
std::unique_lock
1. 可移动但不可复制: std::unique_lock 允许通过移动语义转移锁的所有权,但不能被复制。
2. 灵活性: 可以在构造时延迟锁定,或者在作用域内手动解锁和重新锁定。
3. 支持条件变量: 适合与条件变量(std::condition_variable)一起使用。
示例代码:
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_block(int n) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 等待条件变量
// 临界区
std::cout << "Thread " << n << " is in the critical section\n";
}
int main() {
std::thread t1(print_block, 1);
std::thread t2(print_block, 2);
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟其他工作
{
std::unique_lock<std::mutex> lock(mtx);
ready = true; // 修改共享状态
cv.notify_all(); // 通知所有等待的线程
}
t1.join();
t2.join();
return 0;
}
需要注意的是,如果确定使用unique_lock了,就不要再使用 mutex 的 lock 和 unlock ,直接使用 unique_lock 的 lock 和 unlock,混合使用会导致程序异常,原因是unique_lock 内部会维护一个标识用来记录自己管理的 锁 当前处于何种状态,如果直接使用 mutex的成员函数,unique_lock无法更新自己的状态,从而导致 double lock 和 double unlock(因为unique_lock一定会在析构的时候unlock),这两种情况都会导致崩溃,下面我也会给一个不用lock_guard和unique_lock的示例。
两者的区别
其实就是将上述它们各自的特点概述一下就是区别:
1. std::lock_guard一旦锁定,不能解锁,也不能重新锁定,只能在 std::lock_guard 的析构函数中释放,而std::unique_lock可以在锁定后解锁,并且可以重新锁定。
2. std::lock_guard 不允许被拷贝、复制或移动,而std::unique_lock可移动但不可复制。
3. lock_guard 功能单一简单,只能用作自释放锁,而unique_lock 提供了更多的控制锁的行为,比如锁超时、不锁定、条件变量等,当然付出的代价就是unique_lock开销更大,会慢一点。
使用场景
具体使用哪个,需要具体需求和互斥锁的使用场景来判断:
如果你需要更简单的锁定机制,lock_guard 是一个好选择。如果你需要更复杂的锁管理,unique_lock 提供了更多的控制选项。unique_lock 提供了更多的控制锁的行为,比如锁超时、不锁定、条件变量等。
总结
其实绕来绕去本质上还是 RAII 思想,上述两个代码是我写的两个简单的实例,仅供参考,其实关于锁定的互斥量的使用,记住一点就可以,C++必须要贯彻RAII 思想去写代码,下面是不用lock_guard和unique_lock的示例,如下:
#include <iostream>
#include <mutex>
#include <thread>
class LockGuard {
public:
explicit LockGuard(std::mutex &mtx) : mutex_(mtx) {
mutex_.lock();
}
~LockGuard() {
mutex_.unlock();
}
// 禁止复制
LockGuard(const LockGuard &) = delete;
LockGuard &operator=(const LockGuard &) = delete;
private:
std::mutex &mutex_;
};
// 互斥量
std::mutex mtx;
// 多线程操作的变量
int shared_data = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
// 申请锁
LockGuard lock(mtx);
++shared_data;
// 作用域结束后会析构 然后释放锁
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Shared data: " << shared_data << std::endl;
return 0;
}