当前位置: 首页 > news >正文

SPSC无锁队列

定位

通信模型

核心原理

内部存有两块缓冲区,写缓冲区与读缓冲区
只允许两个线程对SPSC无锁队列进行操作
1个生产者线程只往写缓冲区写入数据
1个消费者线程只往读缓冲区拿取数据
惰性机制:
消费者线程在拿取数据时,会先判断读缓冲区是否为空
只有在读缓冲区空了的时候才会触发原子交换
原子交换
SPSC无锁队列内部存有读缓冲区与写缓冲区的原子指针
这两个指针的交换为原子交换
并且只有消费者线程才会触发原子交换,生产者碰不到

声明与实现

#ifndef SPSC_QUEUE_H
#define SPSC_QUEUE_H
#include <memory>
#include <queue>
#include <atomic>
#include <thread>
#include <functional>template <class T>
class SPSCqueue {
public:typedef std::shared_ptr<SPSCqueue> ptr;SPSCqueue();void        push(T task);                       //添加任务,生产者线程专属T           pop();                              //拿取任务,消费者线程专属bool        empty();                            //判空,读缓冲区的判空
private:void        change();                           //原子交换std::queue<T>                   m_writeQueue;   //写缓冲区std::queue<T>                   m_readQueue;    //读缓冲区std::atomic<std::queue<T> *>    m_writePtr;     //写缓冲区的原子指针std::atomic<std::queue<T> *>    m_readPtr;      //读缓冲区的原子指针
};
template <class T>
SPSCqueue<T>::SPSCqueue() {m_writePtr  = &m_writeQueue;m_readPtr   = &m_readQueue;
}
//添加任务,生产者线程专属
template <class T>
void SPSCqueue<T>::push(T task) {(*m_writePtr).push(std::move(task));
}
//拿取任务,消费者线程专属
template <class T>
T SPSCqueue<T>::pop() {if ((*m_readPtr).empty()) {change();}if (!(*m_readPtr).empty()) {auto task = (*m_readPtr).front();(*m_readPtr).pop();return task;}return nullptr;
}
//判空,读缓冲区的判空
template <class T>
bool SPSCqueue<T>::empty() {return (*m_readPtr).empty();
}
//原子交换
template <class T>
void SPSCqueue<T>::change() {std::queue<T> *writePtr = m_writePtr;std::queue<T> *readPtr = m_readPtr;m_writePtr.exchange(readPtr);m_readPtr.exchange(writePtr);
}#endif

使用示例

#include <chrono>
#include "SPSCqueue.h"SPSCqueue<std::function<void()>>::ptr que;
uint64_t cnt = 0;//生产者线程的入口函数
void producer() {for (long long i = 0; i < 1000000LL; ++i) {que->push([]() {for(int z =0;z<1000;++z) {//模拟具体业务耗时}++cnt; });}
}
//消费者线程的入口函数
void consumer() {while (true) {auto it = que->pop();if (it != nullptr) {it();}}
}
int main() {que = std::make_shared<SPSCqueue<std::function<void()>>>();auto before = std::chrono::system_clock::now(); // 记录开始时刻std::thread t2(consumer);                       // 开启消费者线程std::thread t1(producer);                       // 开启生产者线程while (true) {// 这里会线程不安全// 但是后果只是读取旧值// 所以实际测得的的总耗时结果一定更少if (cnt == 1000000) {auto after = std::chrono::system_clock::now(); // 记录结束时刻auto before_ms = std::chrono::duration_cast<std::chrono::milliseconds>(before.time_since_epoch()).count();auto after_ms = std::chrono::duration_cast<std::chrono::milliseconds>(after.time_since_epoch()).count();std::cout << "开始时刻 : " << before_ms << std::endl;std::cout << "结束时刻 : " << after_ms  << std::endl;std::cout << "耗时 : " << after_ms - before_ms << std::endl; // 输出总耗时break;}}t1.detach();t2.detach();return 0;
}

输出结果

开始时刻 : 1775562352661
结束时刻 : 1775562353289
耗时 : 628

http://www.jsqmd.com/news/603816/

相关文章:

  • ROS2 Jazzy安装后必做的5件事:从验证到跑通第一个机器人Demo
  • 客户决策链地图怎么画:老板、采购、技术、项目、法务分别怎么看你
  • 【触想智能】工业级平板电脑在人机界面上应用的九大特点
  • MySql(基础排序--查找后排序)
  • 如何快速下载E-Hentai漫画合集:终极批量漫画保存工具使用指南
  • seo关键字价格便宜的方法有哪些
  • 如何用思维链提示让ChatGPT变身数学高手?实测效果超乎想象
  • MATLAB仿真m序列、Gold序列、Kasami序列扩频码性能的程序与课程设计报告
  • 【Alger Music】Alger Music Player官网下载:音乐播放器安装使用全攻略 - xiema
  • 大模型剪枝(二)Wanda实战:如何在不重训练的情况下高效压缩LLM
  • MySql(简单处理查询结果--查找后多列排序)
  • 春节必备AI神器:春联生成模型保姆级教程,告别想对联烦恼
  • 记最近这段时间的梦
  • 鸽姆智库(GG3M)深度研究报告:命名体系、理论架构与文明战略分析
  • EPIC账号锁区怎么办?手把手教你通过客服申诉改回国区(附邮件模板)
  • OpenClaw对接百川2-13B-4bits量化版实战:本地部署与飞书机器人配置
  • STM32CubeMX配置RT-Thread Nano:从零构建到任务与内存管理实战
  • 东莞初效过滤器厂家推荐
  • PyWxDump安全指南:微信聊天记录备份与迁移实战手册
  • 特征根法在三对角线型行列式求解中的高效应用
  • 磁链观测器在VESC中使用的方法:实现0速闭环启动的工程实践与代码文档仿真对应
  • QQ空间数据自主权:GetQzonehistory数字记忆保护指南
  • RAG与Python的智能编程教程问答系统:DeepSeek大模型驱动、LangChain流程构建、FAISS向量检索与语义相似度匹配技术实现 |附教程文档
  • Kandinsky-5.0-I2V-Lite-5s惊艳效果展示:小狗眨眼摇头+微风毛发+电影光影真实案例
  • 从 88.3% 到 9.88%!Paperxie AI 降重:毕业论文 AIGC 率 重复率双杀神器
  • 从零到一:手把手教你用苍穹外卖项目搞定Spring Boot多表关联(附完整E-R图与避坑指南)
  • 混合储能系统容量优化配置中的信号分解与容量分配算法解析
  • Legacy-iOS-Kit:让旧款iOS设备重获新生的开源工具完整指南
  • 3步打造专业级媒体解码系统:LAV Filters全方位应用指南
  • SEO网站关键词优化与内容营销有什么关系_SEO网站关键词如何优化