BlockQueue - 高并发环形缓冲阻塞队列
BlockQueue 是基于环形缓冲区实现的线程安全固定容量阻塞队列,采用单锁设计配合双条件变量机制,在保证线程安全的同时追求极致性能。适用于高吞吐低延迟的生产者-消费者场景。
您的star
是对该项目的肯定
核心特性
1. 高效内存模型
- 预分配环形缓冲:初始化时一次性分配连续内存,构成环形单向链表
- 64字节对齐:消除伪共享问题,提升多核缓存利用率
- 条件析构优化:智能跳过trivial类型的析构调用
2. 精准并发控制
- 单锁双条件变量:
notEmptyCond
:数据可读时唤醒消费者notFullCond
:空间可用时唤醒生产者
- 自适应通知:
- 单元素操作使用
notify_one
减少上下文切换 - 批量操作使用
notify_all
提高唤醒效率
- 单元素操作使用
- 停止标志:支持即时优雅关闭
3. 完备操作接口
操作模式 | 非阻塞 | 无限等待 | 超时等待 |
---|---|---|---|
单元素入队 | push() | wait_push() | wait_push(timeout) |
单元素出队 | pop() | wait_pop() | wait_pop(timeout) |
批量入队 | pushBulk() | wait_pushBulk() | wait_pushBulk(timeout) |
批量出队 | popBulk() | wait_popBulk() | wait_popBulk(timeout) |
实现优势
1. 环形缓冲设计
+---+ +---+ +---+ +---+
| 0 | -> | 1 | -> | 2 | -> ... -> |N-1 |
+---+ +---+ +---+ +---+
^ |
|_____________________________|
- O(1)时间复杂度:头尾指针移动完成入队/出队
- 零动态分配:运行期间无内存管理开销
- 缓存局部性:连续内存访问模式,预取友好
2. 条件析构优化
template <typename T, bool IsTrivial = std::is_trivially_destructible<T>::value>
struct DestroyHelper {
static void destroy(T& obj) { obj.~T(); }
};
template <typename T>
struct DestroyHelper<T, true> {
static void destroy(T&) {}
};
template <typename T>
void conditional_destroy(T& obj) {
DestroyHelper<T>::destroy(obj);
}
- 对POD类型跳过析构
3. 批量操作优化
- 单锁批量处理:减少锁竞争频率
- 链式节点访问:直接操作内存连续的预分配节点
- 友好内存布局:数据内存连续排列
使用示例
HSLL::BlockQueue<SensorData> queue;
// 初始化队列(容量1024)
if (!queue.init(1024)) {
throw std::runtime_error("Queue init failed");
}
// 生产者线程
std::thread producer([&] {
SensorData batch[BATCH_SIZE];
while (true) {
unsigned sent = queue.wait_pushBulk(batch, BATCH_SIZE);
if (sent == 0) break; // 队列已停止
// 处理未发送数据...
}
});
// 消费者线程
std::thread consumer([&] {
SensorData results[BATCH_SIZE];
while (unsigned count = queue.wait_popBulk(results, BATCH_SIZE)) {
process_batch(results, count);
}
});
queue.stopWait(); // 优雅关闭
producer.join();
consumer.join();
接口规范
核心方法
方法 | 说明 |
---|---|
bool init(unsigned capacity) | 初始化队列容量(必须先于其他操作调用) |
push()/pop() | 非阻塞操作,立即返回成功状态 |
wait_*() | 阻塞直到操作成功或队列停止 |
wait_*(timeout) | 限时等待版本 |
*Bulk() | 批量处理版本(建议优先使用以获得最佳性能) |
void stopWait() | 触发停止状态并唤醒所有等待线程 |
模板要求
- 元素类型需满足:
- 可移动构造/拷贝构造函数
- 构造/析构函数不抛异常
最佳实践
- 优先使用批量接口:相比单元素操作可提升3-5倍吞吐
- 合理设置容量:根据业务负载特征选择,建议为批次大小的整数倍
- 避免混合操作模式:同一队列不要混用阻塞/非阻塞接口,可能会影响队列效率
- 及时处理停止状态:调用
stopWait()
后需检查队列入/出栈操作的返回值 - 类型设计优化:
- 实现高效的移动构造函数
- 保持较小尺寸(推荐≤64字节)
内部实现要点
内存管理
// Windows平台对齐分配
_aligned_malloc(sizeof(Node)*capacity, 64);
// 其它平台对齐分配
aligned_alloc(64, sizeof(Node)*capacity);
- 保证节点数组64字节对齐
- 消除不同CPU核心间的缓存行竞争
条件变量使用
// 生产者等待空间
notFullCond.wait(lock, [this] {
return size != maxSize || isStopped;
});
// 消费者等待数据
notEmptyCond.wait(lock, [this] {
return size > 0 || isStopped;
});
- 条件判断包含停止状态,避免死锁
- 使用predicate模式防止虚假唤醒
注意事项
- 初始化要求:必须成功调用init()后才可进行其他操作
- 线程安全范围:不同队列实例之间无锁竞争
- 对象生命周期:
- 入队时构造对象(placement new)
- 出队后立即析构(条件触发)
- 异常安全:
- 元素构造函数不应抛出异常
- 队列操作保证强异常安全保证
- 不可复制设计:队列实例禁止拷贝构造和赋值操作