实现阻塞队列
字数 799 2025-11-05 23:47:54
实现阻塞队列
题目描述
阻塞队列是一种特殊的队列,当队列为空时,从队列获取元素的操作会被阻塞,直到队列中有新元素;当队列已满时,向队列添加元素的操作会被阻塞,直到队列中有空闲位置。请实现一个线程安全的阻塞队列。
知识背景
阻塞队列是多线程编程中的重要同步工具,常用于生产者-消费者模型。它通过内部的条件变量和互斥锁来实现线程间的协调。
实现步骤
-
基本结构设计
我们需要一个队列容器(如数组或链表)、一个互斥锁(保证线程安全)和两个条件变量(分别对应队列空和队列满的条件)。 -
核心操作分析
put(element):向队列添加元素。如果队列已满,则阻塞调用线程直到有空位。take():从队列取出元素。如果队列为空,则阻塞调用线程直到有新元素。
- 详细实现过程
步骤1:定义类成员
#include <queue>
#include <mutex>
#include <condition_variable>
template<typename T>
class BlockingQueue {
private:
std::queue<T> queue_; // 底层队列容器
std::mutex mutex_; // 互斥锁
std::condition_variable not_empty_; // 非空条件变量
std::condition_variable not_full_; // 非满条件变量
size_t capacity_; // 队列容量
这里使用标准库的queue作为底层容器,通过mutex实现互斥访问,两个condition_variable分别用于等待非空和非满条件。
步骤2:构造函数初始化
public:
explicit BlockingQueue(size_t capacity) : capacity_(capacity) {}
构造函数接收一个容量参数,用于指定队列的最大长度。
步骤3:实现put方法
void put(const T& item) {
std::unique_lock<std::mutex> lock(mutex_);
// 等待队列非满(条件变量的标准用法:循环检查防止虚假唤醒)
not_full_.wait(lock, [this]() {
return queue_.size() < capacity_;
});
queue_.push(item); // 添加元素到队列
not_empty_.notify_one(); // 通知可能等待的消费者
}
- 获取互斥锁后,检查队列是否已满
- 如果已满,则通过wait释放锁并阻塞,直到被其他线程唤醒
- 添加元素后,通过notify_one唤醒一个可能等待的消费者线程
步骤4:实现take方法
T take() {
std::unique_lock<std::mutex> lock(mutex_);
// 等待队列非空
not_empty_.wait(lock, [this]() {
return !queue_.empty();
});
T item = queue_.front(); // 获取队首元素
queue_.pop(); // 移除队首元素
not_full_.notify_one(); // 通知可能等待的生产者
return item;
}
- 与put方法对称,先等待队列非空
- 取出元素后通知可能阻塞的生产者线程
步骤5:可选辅助方法
bool empty() const {
std::lock_guard<std::mutex> lock(mutex_);
return queue_.empty();
}
size_t size() const {
std::lock_guard<std::mutex> lock(mutex_);
return queue_.size();
}
这些辅助方法也需要加锁保证线程安全。
关键要点
- 条件变量使用模式:总是使用while循环(或lambda表达式)检查条件,防止虚假唤醒
- RAII锁管理:使用unique_lock自动管理锁的获取和释放
- 通知时机:在改变条件状态后立即通知等待的线程
- 异常安全:确保在异常情况下锁能被正确释放
这个实现提供了阻塞队列的核心功能,可以安全地在多线程环境下使用,是理解线程同步的经典案例。