一文讲通锁标记对象std::adopt_lock盲点

发布于:2025-03-15 ⋅ 阅读:(16) ⋅ 点赞:(0)

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] 锁标记对象