MST

星途 面试题库

面试题:C++ 条件变量在对象间数据共享并发问题中的应用

现有一个生产者 - 消费者模型,多个生产者线程向共享队列中添加数据,多个消费者线程从共享队列中取出数据。请用 C++ 代码实现这个模型,要求使用 `std::condition_variable` 来协调生产者和消费者线程,确保线程安全的数据共享。同时分析 `std::condition_variable` 的使用场景及可能遇到的死锁问题,并说明如何避免。
20.2万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

C++ 代码实现生产者 - 消费者模型

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::queue<int> sharedQueue;
std::mutex queueMutex;
std::condition_variable cv;
const int queueSize = 5;

// 生产者线程函数
void producer(int id) {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(queueMutex);
        cv.wait(lock, [] { return sharedQueue.size() < queueSize; });
        sharedQueue.push(id * 10 + i);
        std::cout << "Producer " << id << " pushed " << id * 10 + i << std::endl;
        lock.unlock();
        cv.notify_one();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

// 消费者线程函数
void consumer(int id) {
    while (true) {
        std::unique_lock<std::mutex> lock(queueMutex);
        cv.wait(lock, [] { return!sharedQueue.empty(); });
        int data = sharedQueue.front();
        sharedQueue.pop();
        std::cout << "Consumer " << id << " popped " << data << std::endl;
        lock.unlock();
        cv.notify_one();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

你可以通过以下方式调用:

int main() {
    std::thread producers[3];
    std::thread consumers[2];

    for (int i = 0; i < 3; ++i) {
        producers[i] = std::thread(producer, i);
    }

    for (int i = 0; i < 2; ++i) {
        consumers[i] = std::thread(consumer, i);
    }

    for (auto& producer : producers) {
        producer.join();
    }

    for (auto& consumer : consumers) {
        consumer.join();
    }

    return 0;
}

std::condition_variable 使用场景

  1. 线程间同步:当一个线程需要等待某个条件满足才能继续执行时,例如消费者线程等待队列中有数据,生产者线程等待队列有空闲空间。
  2. 资源管理:在多线程环境下,控制对共享资源的访问。当资源达到某种状态(如满或空)时,通知相关线程进行相应操作。

可能遇到的死锁问题及避免方法

  1. 死锁问题
    • 缺少通知:如果生产者或消费者线程忘记调用 cv.notify_one()cv.notify_all(),那么等待的线程将永远阻塞,导致死锁。
    • 锁的持有问题:如果在调用 cv.wait 时没有正确释放锁(例如手动提前解锁),或者在 cv.wait 返回后没有重新获取锁,可能导致数据竞争和死锁。例如,消费者线程在 cv.wait 后,没有重新获取锁就访问共享队列,可能导致其他线程无法修改队列状态,造成死锁。
  2. 避免方法
    • 确保通知:在条件满足时,务必调用 cv.notify_one()cv.notify_all()。例如,生产者在往队列中添加数据后,调用 cv.notify_one() 通知消费者;消费者在从队列中取出数据后,调用 cv.notify_one() 通知生产者。
    • 正确使用锁:使用 std::unique_lock 来管理锁,并将其作为参数传递给 cv.waitstd::unique_lock 会在 cv.wait 时自动释放锁,并在 cv.wait 返回时重新获取锁,避免手动管理锁带来的错误。例如在生产者和消费者函数中,使用 std::unique_lock<std::mutex> lock(queueMutex); 来管理锁,并将 lock 传递给 cv.wait