一文讲通锁标记对象std::adopt_lock盲点
- 1. 核心概念
- 2. 代码详解
- 1. 单个锁
- 2. 多重锁(可以用来预防死锁)
- 3. 条件变量的互斥控制
- 4. 复杂示例: 多生产者-多消费者模型(超纲了, 可不看,哈哈哈哈)
- 3. 小结
1. 核心概念
在C++中, std::adopt_lock是一个锁标记对象[^1], 用于配合锁对象(如 std::lock_guard、std::unique_lock 或 std::shared_lock)来管理互斥锁(mutex), 它的作用是告诉锁对象:互斥锁已经被手动锁定了,锁对象只需要“接管”这个锁的所有权,而不需要再次尝试锁定。
2. 代码详解
1. 单个锁
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx; // Global mutex
void process() {
// Step 1: Manually lock the mutex.
mtx.lock(); // The current thread now owns the mutex.
// Step 2: Construct a lock_guard to adopt the existing lock.
// The std::adopt_lock tag tells lock_guard that mtx is already locked.
std::lock_guard<std::mutex> guard(mtx, std::adopt_lock);
// Critical section: safe access to shared resources.
std::cout << "Inside critical section." << std::endl;
// When 'guard' goes out of scope, its destructor will call mtx.unlock().
}
int main() {
std::thread t(process);
t.join(); // Wait for the thread to finish.
return 0;
}
这里的关键点是 std::adopt_lock,它告诉 lock_guard 互斥量已经被锁定,因此 lock_guard 不会尝试再次调用
mtx.lock()。
如果没有使用 std::adopt_lock,std::lock_guard 会在构造时试图锁定互斥量,这将导致死锁。
2. 多重锁(可以用来预防死锁)
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx1;
std::mutex mtx2;
void task() {
// Step 1: Atomically lock both mutexes using std::lock, which prevents deadlock.
std::lock(mtx1, mtx2);
// Step 2: Create RAII objects that adopt these locks.
std::lock_guard<std::mutex> guard1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> guard2(mtx2, std::adopt_lock);
// Critical section: safe access to shared resources protected by mtx1 and mtx2.
std::cout << "Thread safely acquired both mutexes." << std::endl;
// Both mutexes will be unlocked when guard1 and guard2 go out of scope.
}
int main() {
std::thread t1(task);
std::thread t2(task);
t1.join();
t2.join();
return 0;
}
3. 条件变量的互斥控制
在条件变量中结合 std::unique_lock 和 std::adopt_lock 使用,可以灵活地管理锁的状态。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void workerThread() {
// 手动锁定互斥量
mtx.lock();
std::cout << "Worker thread acquired lock manually.\n";
// 使用 adopt_lock 转移锁管理权
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);
// 等待条件变量被通知
cv.wait(lock, [] { return ready; });
std::cout << "Worker thread is processing data.\n";
}
void notifierThread() {
std::this_thread::sleep_for(std::chrono::seconds(1));
// 通知条件变量
{
std::lock_guard<std::mutex> lock(mtx); // 自动管理锁
ready = true;
std::cout << "Notifier thread is notifying.\n";
}
cv.notify_one();
}
int main() {
std::thread worker(workerThread);
std::thread notifier(notifierThread);
worker.join();
notifier.join();
return 0;
}
解释补充
- worker_thread 手动锁定互斥量并通过 std::adopt_lock 转移锁管理权给 std::unique_lock。
- notifier_thread 通过 std::lock_guard 安全通知条件变量。
4. 复杂示例: 多生产者-多消费者模型(超纲了, 可不看,哈哈哈哈)
假设有多个生产者线程向共享队列中添加任务,而多个消费者线程从队列中处理任务。为了避免死锁,使用 std::adopt_lock 配合多个互斥量和条件变量。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
#include <chrono>
std::mutex queue_mutex; // 队列的互斥量
std::mutex print_mutex; // 打印输出的互斥量
std::condition_variable cv; // 条件变量
std::queue<int> task_queue; // 共享任务队列
const int MAX_QUEUE_SIZE = 10; // 队列的最大容量
bool stop = false; // 用于通知消费者线程停止
// 生产者函数
void producer(int id) {
for (int i = 0; i < 20; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产延迟
std::unique_lock<std::mutex> lock(queue_mutex); // 锁定队列的互斥量
cv.wait(lock, [] { return task_queue.size() < MAX_QUEUE_SIZE; }); // 等待队列有空间
task_queue.push(i);
{
// 使用 adopt_lock 安全打印日志
std::lock_guard<std::mutex> print_lock(print_mutex, std::adopt_lock);
std::cout << "Producer " << id << " produced task " << i << ". Queue size: " << task_queue.size() << std::endl;
}
cv.notify_all(); // 通知消费者
}
}
// 消费者函数
void consumer(int id) {
while (true) {
std::unique_lock<std::mutex> lock(queue_mutex);
cv.wait(lock, [] { return !task_queue.empty() || stop; }); // 等待队列有任务或停止信号
if (stop && task_queue.empty()) break; // 如果停止并且队列为空,退出
int task = task_queue.front();
task_queue.pop();
{
// 使用 adopt_lock 安全打印日志
std::lock_guard<std::mutex> print_lock(print_mutex, std::adopt_lock);
std::cout << "Consumer " << id << " consumed task " << task << ". Queue size: " << task_queue.size() << std::endl;
}
cv.notify_all(); // 通知生产者
}
}
int main() {
std::vector<std::thread> producers;
std::vector<std::thread> consumers;
// 启动生产者线程
for (int i = 0; i < 3; ++i) {
producers.emplace_back(producer, i + 1);
}
// 启动消费者线程
for (int i = 0; i < 2; ++i) {
consumers.emplace_back(consumer, i + 1);
}
// 等待所有生产者完成
for (auto& p : producers) {
p.join();
}
// 通知消费者停止
{
std::lock_guard<std::mutex> lock(queue_mutex);
stop = true;
}
cv.notify_all();
// 等待所有消费者完成
for (auto& c : consumers) {
c.join();
}
std::cout << "All tasks processed. Exiting program." << std::endl;
return 0;
}
3. 小结
std::adopt_lock 适用于以下场景:
- Lock First, Adopt Later:手动锁定互斥量后,将管理权交给锁管理类(如 std::lock_guard 或 std::unique_lock)。
- 注释要清晰(就是这么突兀! 没错, 就是没有这个场景!!!, 想想我为什么要写?还标红加粗)
- 多个互斥量的协调锁定。
- 配合条件变量灵活管理锁定逻辑。
[^1] 锁标记对象