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)
上一篇 2025年12月11日 下午1:01
下一篇 2025年12月11日 下午1:22


相关推荐

  • SpringBoot 快速开启事务(附常见坑点)「建议收藏」

    做一个积极的人编码、改bug、提升自己我有一个乐园,面向编程,春暖花开!序言:此前,我们主要通过XML配置Spring来托管事务。在SpringBoot则非常简单,只需在业务层添加事务注解(@Transactional )即可快速开启事务。虽然事务很简单,但对于数据方面是需要谨慎对待的,识别常见坑点对我们开发有帮助。1.引入依赖 <!–依赖管理 …

    2022年2月28日
    62
  • eclipse方法自动注释_eclipse快速补全

    eclipse方法自动注释_eclipse快速补全1、Eclipse自动补全功能设置,默认是键入“.”才会有代码提示,否则就只有按“Alt+/”组合键。通过下面的设置可以按照你自己的需求显示代码提示。1)、直接设置打开Eclipse->Window->Perferences->Java->Editor->ContentAssist,右边出现的选项中,有一个AutoactivationtriggersorforJava

    2022年10月9日
    4
  • 如何干净的卸载mysql_软件卸载了权限还在吗

    如何干净的卸载mysql_软件卸载了权限还在吗如何完美的卸载掉Mysql?按以下几个步骤去执行。步骤一确认你的mysql服务是关闭的状态,不然卸载不干净。在我的电脑(计算机)–管理–服务和应用程序–服务,找到mysql把状态关闭。步骤二在控制面板中卸载mysql软件。步骤三卸载过后删除C:ProgramFiles(x86)\MySQL该目录下剩余了所有文件,把mysql文件夹也删了步骤四window…

    2022年9月30日
    4
  • java中break和continue的用法「建议收藏」

    java中break和continue的用法「建议收藏」**break和continue的用法**break的用法:1.break用于switch语句中,终止switch语句2.break用于循环时,跳出循环3.break用于其他位置,毫无意义1.break用于switch语句中,终止switch语句inta=4;switch(a){case1:…

    2022年4月30日
    59
  • ggplot2 绘制火山图

    ggplot2 绘制火山图使用 ggplot 包绘制火山图 需要一个 dataframe 并包含如下信息 log2FoldChan 绘制 x 轴 pvalue 或 padj 绘制 y 轴 Change 元素为 up down 或 none 用于散点上色 library ggplot2 一 添加 Change 列 View result log2FC 阈值 0 5 padj 阈值 0 05result which result log2FoldChan gt 0 5 amp result padj

    2026年3月26日
    2
  • 千问3.5-27B部署教程:conda env qwen3527环境依赖与版本锁定说明

    千问3.5-27B部署教程:conda env qwen3527环境依赖与版本锁定说明

    2026年3月16日
    3

发表回复

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

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