C++ 无锁队列

C++ 无锁队列atomic 类型的操作是原子操作 是不可分割的 不能被中断的操作 程序代码中的一条简单赋值语句会被翻译为多条汇编指令 那么多个线程同时对某一存储单元进行修改 就有可能出现脏数据 原子操作可以避免脏数据的出现 2 多线程读写三 总结无锁队列依靠原子和 CAS 操作 对队列的读写索引进行判断来入队和出队 它没有使用互斥量 mutex 来进行加锁 从性能上具有明显的优势 但同时编程的复杂性增加了很多 在编码时也要对内存序有简单的了解


一、atomic原子类型

二、无锁队列

1.代码实现

#include <thread> #include <iostream> #include <atomic> #include <vector> using namespace std; static const int QUEUE_SIZE = 1024; static const int PRODUCES_NUMBER = 3; static const int CONSUMERS_NUMBER = 4; static const int MSG_NUMBER = 200; template<typename T> struct Node { 
     T data; //数据 atomic_bool hasData; //是否有数据 }; template<typename T> class Queue { 
     public: Queue():msg(QUEUE_SIZE) { 
     r_index = 0; w_index = 0; }; bool enqueue(T& value); // 入队列 bool dequeue(T& value); // 出队列 private: vector<Node<T>> msg; // 消息队列 atomic<size_t> r_index; // 读索引(不考虑溢出情况) atomic<size_t> w_index; // 写索引(不考虑溢出情况) }; template<typename T> bool Queue<T>::enqueue(T& value) { 
     size_t l_w_index = w_index.load(std::memory_order_relaxed); Node<T>* node = NULL; // CAS比较是否和预期一致,一致则对写索引递增1,不一致则重试 do { 
     //队列是否已满 if (l_w_index >= r_index.load(std::memory_order_relaxed) + msg.size()) { 
     return false; } // 判断是否有数据 size_t index = l_w_index % msg.size(); node = &msg[index]; if (node->hasData.load(std::memory_order_relaxed)) { 
     return false; } } while (!w_index.compare_exchange_weak(l_w_index, l_w_index + 1, std::memory_order_relaxed)); //写数据 node->data = std::move(value);//左值转右值,避免拷贝带来的浪费 node->hasData.store(true); return true; } template<typename T> bool Queue<T>::dequeue(T& value) { 
     size_t l_r_index = r_index.load(std::memory_order_relaxed);; Node<T>* node = NULL; // CAS比较是否和预期一致,一致则对读索引递增1,不一致则重试 do { 
     //队列是否为空 if (l_r_index > w_index.load(std::memory_order_relaxed)) { 
     return false; } // 判断是否有数据 size_t index = l_r_index % msg.size(); node = &msg[index]; if (!node->hasData.load(std::memory_order_relaxed)) { 
     return false; } } while (!r_index.compare_exchange_weak(l_r_index, l_r_index + 1, std::memory_order_relaxed)); //读数据 value = std::move(node->data); node->hasData.store(false); return true; } 

2. 多线程读写

 int main() { 
     //控制线程 std::atomic<uint8_t> producer_thread_count = 0; std::atomic<uint8_t> consumer_thread_count = 0; //队列 Queue<uint32_t> queue; //消息序列 std::atomic<uint32_t> sequence = 0; //生产者线程 auto producer = [&queue, &sequence, &producer_thread_count]() { 
     for (uint32_t i = 0; i < MSG_NUMBER; i++) { 
     uint32_t num = sequence++; while(!queue.enqueue(num));//入队列[0, (MSG_NUMBER-1) * PRODUCES_NUMBER) } producer_thread_count++; }; //消费者线程 std::atomic<uint32_t> counter[MSG_NUMBER * PRODUCES_NUMBER]; auto consumer = [&queue, &counter,&consumer_thread_count]() { 
     uint32_t num = 0; while (queue.dequeue(num)) { 
     counter[num]++;//出队列后把对应索引位的值递增1 } consumer_thread_count++; }; //线程池 std::unique_ptr<std::thread> produce_threads[PRODUCES_NUMBER]; std::unique_ptr<std::thread> consumer_threads[CONSUMERS_NUMBER]; //创建线程 for (int i = 0; i < PRODUCES_NUMBER; i++) { 
     produce_threads[i].reset(new std::thread(producer)); produce_threads[i]->detach(); } for (int i = 0; i < CONSUMERS_NUMBER; i++) { 
     consumer_threads[i].reset(new std::thread(consumer)); consumer_threads[i]->detach(); } while (producer_thread_count != PRODUCES_NUMBER || consumer_thread_count != CONSUMERS_NUMBER) { 
     std::this_thread::sleep_for(std::chrono::seconds(5)); } //判断是否有竞争 for (int i = 0; i < (MSG_NUMBER*PRODUCES_NUMBER); i++) { 
     if (counter[i] != 1) { 
     std::cout << "found race condition\t" << i << '\t' << counter[i] << std::endl; break; } } return 0; } 

三、总结

无锁队列依靠原子和CAS操作,对队列的读写索引进行判断来入队和出队,它没有使用互斥量mutex来进行加锁,从性能上具有明显的优势,但同时编程的复杂性增加了很多,在编码时也要对内存序有简单的了解。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/231463.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 时序数据预测:ARIMA

    时序数据预测:ARIMA本文尝试应用 ARIMA 时间序列模型对具有明显季节规律的月度时序数据进行预测 样本数据来源于本人项目工作中的某地区某行业电量 已脱敏处理 外加搜集了部分外部宏观经济 气象数据 时间跨度 2017 年 1 月至今 思路 将原始时序数据进行周期分解为趋势部分 周期部分 残差部分 趋势部分应用 ARIMA 建模预测 周期部分取历年月均值 残差部分计算残差上界 残差下界并应用 Lasso 回归模型基于外部影响因素建模预测 最后对各部分结果采用不同方案进行叠加 经判断后选取最合理的方案结果作为最终预测结果 本文成果开发

    2025年5月9日
    9
  • 阿里云申请免费ssl证书及安装_阿里云服务器升级配置

    阿里云申请免费ssl证书及安装_阿里云服务器升级配置阿里云购买免费SSL证书1.操作环境2.购买免费SSL证书3.证书申请,绑定域名《下一篇:Nginx+SSL证书,配置https》1.操作环境阿里云(已实名的企业账号)2.购买免费SSL证书2.1.如图,阿里云官网登陆后,搜索框内输入SSL,点击证书控制台,进入后点击购买证书2.2.如图,选中单域名–DVSSL–免费版,点击立即购买2.3.如图,勾选阅读,点击支付,在支付成功界面,点击证书控制台3.证书申请,绑定域名3.1.如图,在证书控制台,点击证书申请

    2022年10月3日
    5
  • 我的家庭私有云计划-17

    我的家庭私有云计划-17

    2021年8月17日
    61
  • 《MySQL45讲》读书笔记(三):内存数据刷盘机制

    《MySQL45讲》读书笔记(三):内存数据刷盘机制此文为极客时间:MySQL实战45讲的12节的学习笔记一、mysql的刷盘机制而之前提到过,mysql使用了WAL技术,即更新的时候先更新内存中的数据,然后必要的时候再将内存中的数据刷入磁

    2022年8月16日
    5
  • pythoncharm注释快捷键_多行注释以什么开头

    pythoncharm注释快捷键_多行注释以什么开头PyCharm多行注释快捷键为Ctrl+/。

    2022年8月29日
    3
  • linux smartctl 命令,Smartctl 命令查看硬盘详细信息

    linux smartctl 命令,Smartctl 命令查看硬盘详细信息Smartctl命令查看硬盘详细信息(2011-08-3014:21:41)标签:linux硬盘信息使用时间杂谈1.1什么是Smartmontools?Smartmontools是一种硬盘检测工具,通过控制和管理硬盘的SMART(SelfMonitoringAnalysisandReportingTechnology,自动检测分析及报告技术)技术来实现的,SMART技术可以对硬盘的磁头单…

    2022年6月17日
    53

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号