lock_guard、unique_lock
在 C++ 多线程编程中, lock_guard 和 unique_lock 都是用于管理互斥量(mutex)的 RAII 封装类,确保互斥量能被正确释放,避免死锁。
1. lock_guard
基本概念
lock_guard 是一个轻量级的、不可移动复制的 RAII 包装器,它在构造时锁定互斥量,在析构时自动解锁。
常用功能
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
std::mutex mtx;
int shared_data = 0;
void increment_lock_guard(int n) {
for (int i = 0; i < n; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 构造时自动加锁
++shared_data;
// 离开作用域时自动解锁
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment_lock_guard, 1000);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Final value: " << shared_data << std::endl; // 5000
return 0;
}
特点
- 构造即加锁:创建时自动锁定互斥量
- 析构自动解锁:离开作用域时自动解锁
- 不可手动操作:不能提前解锁或重新加锁
- 简单高效:几乎没有额外开销
2. unique_lock
基本概念
unique_lock 是一个功能更丰富的 RAII 包装器,提供了更大的灵活性。
常用功能示例
基本使用
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker_thread() {
std::unique_lock<std::mutex> lock(mtx); // 构造时加锁
// 等待条件变量(会自动解锁并重新加锁)
cv.wait(lock, []{ return ready; });
std::cout << "Worker thread is processing...\n";
// lock 析构时自动解锁
}
void main_thread() {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one();
}
延迟加锁
cpp
void delayed_lock_example() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟加锁
// 做一些不需要锁的操作
std::cout << "Doing some work without lock...\n";
// 需要时手动加锁
lock.lock();
// 操作共享数据
lock.unlock(); // 可以手动解锁
// 还可以重新加锁
lock.lock();
}
尝试加锁
cpp
bool try_lock_example() {
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
// 成功获取锁
std::cout << "Lock acquired successfully\n";
return true;
} else {
// 未获取锁,做其他工作
std::cout << "Failed to acquire lock, doing alternative work\n";
return false;
}
}
带超时的加锁
cpp
bool timed_lock_example() {
std::unique_lock<std::mutex> lock(mtx, std::chrono::milliseconds(100));
if (lock.owns_lock()) {
std::cout << "Lock acquired within timeout\n";
return true;
} else {
std::cout << "Timeout: failed to acquire lock\n";
return false;
}
}
unique_lock 的主要功能特性
| 功能 | 描述 |
|---|---|
lock() |
手动加锁 |
unlock() |
手动解锁 |
try_lock() |
尝试加锁,立即返回 |
try_lock_for() |
在一段时间内尝试加锁 |
try_lock_until() |
在指定时间点前尝试加锁 |
owns_lock() |
检查是否持有锁 |
release() |
释放互斥量的所有权但不解锁 |
mutex() |
获取底层互斥量指针 |
3. 两者的比较
| 特性 | lock_guard | unique_lock |
|---|---|---|
| 手动加锁/解锁 | 不支持 | 支持 |
| 延迟初始化 | 不支持 | 支持 |
| 条件变量 | 不兼容 | 兼容 |
| 尝试加锁 | 不支持 | 支持 |
| 超时加锁 | 不支持 | 支持 |
| 性能开销 | 最小 | 稍大 |
| 所有权转移 | 不支持 | 支持移动 |
4. 使用建议
使用 lock_guard 的情况
cpp
// 简单作用域内的锁保护
void simple_function() {
std::lock_guard<std::mutex> lock(mtx); // 推荐
// 操作共享资源
// 自动解锁
}
使用 unique_lock 的情况
cpp
// 需要条件变量的情况
void condition_variable_example() {
std::unique_lock<std::mutex> lock(mtx); // 必须使用 unique_lock
cv.wait(lock, []{ return condition; });
}
// 需要手动控制锁的情况
void manual_control() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// 复杂的加锁逻辑
if (some_condition) {
lock.lock();
// ...
lock.unlock();
}
}
锁的策略参数
cpp
// 延迟加锁(构造时不加锁)
std::unique_lock<std::mutex> lock1(mtx, std::defer_lock);
// 尝试加锁(构造时尝试)
std::unique_lock<std::mutex> lock2(mtx, std::try_to_lock);
// 假设已加锁(构造时不加锁)
std::unique_lock<std::mutex> lock3(mtx, std::adopt_lock);
5. 实际示例:线程安全的队列
cpp
#include <queue>
#include <mutex>
#include <condition_variable>
template<typename T>
class ThreadSafeQueue {
private:
std::queue<T> queue_;
mutable std::mutex mtx_;
std::condition_variable cv_;
public:
void push(T value) {
std::lock_guard<std::mutex> lock(mtx_); // 简单作用域,使用 lock_guard
queue_.push(std::move(value));
cv_.notify_one();
}
bool try_pop(T& value) {
std::lock_guard<std::mutex> lock(mtx_);
if (queue_.empty()) {
return false;
}
value = std::move(queue_.front());
queue_.pop();
return true;
}
void wait_and_pop(T& value) {
std::unique_lock<std::mutex> lock(mtx_); // 需要条件变量,使用 unique_lock
cv_.wait(lock, [this]{ return !queue_.empty(); });
value = std::move(queue_.front());
queue_.pop();
}
};
总结
lock_guard:简单、高效,适合大多数不需要复杂控制的情况unique_lock:功能丰富、灵活,适合需要条件变量、手动控制或超时机制的情况
在实际开发中,优先考虑使用 lock_guard,只有在需要 unique_lock 的特定功能时才使用它,因为 lock_guard 的性能开销更小。