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

C++ 无锁队列:原理与实现

引言

在多线程编程中,队列是一种常用的数据结构。传统的队列在多线程环境下访问时,通常需要使用锁机制来保证数据的一致性和线程安全。然而,锁的使用会带来性能开销,尤其是在高并发场景下,频繁的加锁和解锁操作可能成为性能瓶颈。无锁队列作为一种高效的替代方案,通过使用原子操作和巧妙的内存管理,避免了锁的使用,从而提升了多线程环境下的性能。本文将深入探讨 C++ 中无锁队列的实现原理,并给出详细的代码示例。

无锁队列的原理

无锁队列基于 CAS(Compare - And - Swap)操作来实现。CAS 是一种原子操作,它将内存位置的内容与给定值进行比较,如果相等,则将该内存位置的内容修改为新的值。在无锁队列中,入队和出队操作通过 CAS 操作来更新队列的头部和尾部指针,从而避免了锁的使用。

假设有一个简单的节点结构用于构成队列:

 

template<typename T>

struct Node {

T data;

std::atomic<Node*> next;

Node(const T& value) : data(value), next(nullptr) {}

};

这里的next指针被声明为std::atomic<Node*>类型,以确保对其进行的操作是原子的,避免多线程环境下的竞态条件。

入队操作

入队操作的基本步骤如下:

  1. 创建一个新节点,将数据放入新节点。
  1. 找到队列的尾部节点。
  1. 使用 CAS 操作将新节点链接到尾部节点的next指针上。
  1. 如果 CAS 操作失败,说明在找到尾部节点后,队列发生了变化(其他线程进行了入队或出队操作),需要重新执行步骤 2 和 3。
  1. 成功链接新节点后,使用 CAS 操作更新队列的尾部指针指向新节点。

出队操作

出队操作的基本步骤如下:

  1. 检查队列是否为空。
  1. 找到队列的头部节点。
  1. 获取头部节点的下一个节点。
  1. 使用 CAS 操作将队列的头部指针更新为下一个节点。
  1. 如果 CAS 操作失败,说明在获取头部节点后,队列发生了变化,需要重新执行步骤 2 到 4。
  1. 释放旧的头部节点。

无锁队列的实现代码

 

template<typename T>

class LockFreeQueue {

private:

std::atomic<Node<T>*> head;

std::atomic<Node<T>*> tail;

public:

LockFreeQueue() {

Node<T>* sentinel = new Node<T>(T());

head.store(sentinel);

tail.store(sentinel);

}

~LockFreeQueue() {

while (Node<T>* node = head.load()) {

head.store(node->next.load());

delete node;

}

}

void enqueue(const T& value) {

Node<T>* newNode = new Node<T>(value);

while (true) {

Node<T>* tailNode = tail.load();

Node<T>* nextNode = tailNode->next.load();

if (tailNode == tail.load()) {

if (!nextNode) {

if (tailNode->next.compare_exchange_weak(nextNode, newNode)) {

tail.compare_exchange_weak(tailNode, newNode);

return;

}

}

else {

tail.compare_exchange_weak(tailNode, nextNode);

}

}

}

}

bool dequeue(T& result) {

while (true) {

Node<T>* headNode = head.load();

Node<T>* tailNode = tail.load();

Node<T>* nextNode = headNode->next.load();

if (headNode == head.load()) {

if (headNode == tailNode) {

if (!nextNode) {

return false;

}

tail.compare_exchange_weak(tailNode, nextNode);

}

else {

result = nextNode->data;

if (head.compare_exchange_weak(headNode, nextNode)) {

delete headNode;

return true;

}

}

}

}

}

};

代码解析

  1. 构造函数:在构造函数中,创建一个哨兵节点(sentinel node),并将head和tail指针都指向该节点。哨兵节点用于简化边界条件的处理,避免在队列为空时进行额外的判断。
  1. 析构函数:析构函数遍历队列,释放所有节点的内存。它通过不断地加载head指针,并将其更新为下一个节点,然后删除当前节点,直到head指针为空。
  1. 入队函数enqueue
    • 首先创建一个新节点。
    • 进入一个无限循环,在循环中不断尝试找到队列的尾部节点。
    • 检查当前找到的尾部节点的next指针是否为空。如果为空,尝试使用compare_exchange_weak(一种 CAS 操作)将新节点链接到尾部节点的next指针上。如果链接成功,再尝试更新tail指针指向新节点。
    • 如果当前找到的尾部节点的next指针不为空,说明有其他线程在当前线程找到尾部节点后进行了入队操作,此时需要更新tail指针,使其指向next指针所指向的节点,然后重新尝试入队。
  1. 出队函数dequeue
    • 进入一个无限循环,在循环中不断尝试找到队列的头部节点。
    • 检查队列是否为空(即head和tail指针是否指向同一个节点且该节点的next指针为空)。如果为空,返回false表示出队失败。
    • 如果队列不为空,获取头部节点的下一个节点的数据。
    • 使用compare_exchange_weak尝试更新head指针,使其指向头部节点的下一个节点。如果更新成功,说明出队操作成功,删除旧的头部节点并返回true。
    • 如果更新失败,说明有其他线程在当前线程获取头部节点后进行了入队或出队操作,此时需要重新尝试出队。

无锁队列使用示例

下面是一个简单的多线程环境中使用无锁队列的示例代码,展示了如何创建无锁队列对象,以及多个线程同时进行入队和出队操作:

 

#include <iostream>

#include <thread>

#include <vector>

#include "LockFreeQueue.h" // 假设无锁队列实现代码在该头文件中

void producer(LockFreeQueue<int>& queue, int value) {

queue.enqueue(value);

std::cout << "Produced: " << value << std::endl;

}

void consumer(LockFreeQueue<int>& queue) {

int result;

if (queue.dequeue(result)) {

std::cout << "Consumed: " << result << std::endl;

}

else {

std::cout << "Queue is empty" << std::endl;

}

}

int main() {

LockFreeQueue<int> queue;

std::vector<std::thread> threads;

// 创建多个生产者线程

for (int i = 0; i < 3; ++i) {

threads.emplace_back(producer, std::ref(queue), i * 10);

}

// 创建多个消费者线程

for (int i = 0; i < 3; ++i) {

threads.emplace_back(consumer, std::ref(queue));

}

// 等待所有线程完成

for (auto& thread : threads) {

thread.join();

}

return 0;

}

在上述示例中,定义了producer和consumer函数,分别用于向无锁队列中入队和出队数据。在main函数中,创建了一个LockFreeQueue<int>对象,并启动了多个生产者线程和消费者线程。生产者线程向队列中插入不同的值,消费者线程从队列中取出数据并打印。通过这种方式,可以直观地看到无锁队列在多线程环境下的工作情况。

性能优势与适用场景

无锁队列在高并发场景下具有显著的性能优势,因为它避免了锁带来的线程阻塞和上下文切换开销。在多线程频繁访问队列的情况下,无锁队列能够提供更高的吞吐量和更低的延迟。然而,无锁队列的实现相对复杂,代码可读性和可维护性较差。因此,在低并发场景下,或者对代码复杂度有严格要求的场景中,传统的带锁队列可能是更合适的选择。

总结

无锁队列是一种在多线程编程中非常有用的数据结构,通过巧妙地使用原子操作和内存管理,避免了锁的使用,从而提升了性能。本文详细介绍了无锁队列的原理,并给出了完整的 C++ 实现代码及使用示例。希望通过本文的介绍,读者能够深入理解无锁队列的工作机制,并在实际项目中灵活运用这一技术。

相关文章:

C++ 无锁队列:原理与实现

引言 在多线程编程中&#xff0c;队列是一种常用的数据结构。传统的队列在多线程环境下访问时&#xff0c;通常需要使用锁机制来保证数据的一致性和线程安全。然而&#xff0c;锁的使用会带来性能开销&#xff0c;尤其是在高并发场景下&#xff0c;频繁的加锁和解锁操作可能成…...

web的分离不分离:前后端分离与不分离全面分析

让我们一起走向未来 &#x1f393;作者简介&#xff1a;全栈领域优质创作者 &#x1f310;个人主页&#xff1a;百锦再新空间代码工作室 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[1504566…...

MobileSal:极其高效的RGB-D显著性物体检测模型

摘要 问题一&#xff1a;什么叫做MobileSal&#xff1f; MobileSal 是指一种用于移动设备上的显著性检测&#xff08;Saliency Detection&#xff09;方法&#xff0c;通常是针对在资源受限的环境&#xff08;如智能手机&#xff09;上运行的视觉模型。 问题二&#xff1a;什…...

【个人总结】1. 开发基础 工作三年的嵌入式常见知识点梳理及开发技术要点(欢迎指正、补充)

【个人总结】1. 开发基础 工作三年的嵌入式常见知识点梳理及开发技术要点&#xff08;欢迎指正、补充&#xff09; 工作快三年以来 分别进行了嵌入式MCU及外设开发、RTOS、传感器、文件系统及USB、Linux、GUI、通讯协议、毫米波雷达、少量的DSP和物联网开发。 特此总结&#x…...

硬核技术组合!用 DeepSeek R1、Ollama、Docker、RAGFlow 打造专属本地知识库

文章目录 一、引言二、安装Ollama部署DeepSeekR1三、安装Docker四、安装使用RAGFlow4.1 系统架构4.2 部署流程4.3 使用RAGFlow4.4 在RAGFlow中新增模型4.5 创建知识库4.6 创建私人助理使用RGA 一、引言 本地部署DeepSeek R1 Ollama RAGFlow构建个人知识库&#xff0c;通过将…...

MySQL官网驱动下载(jar包驱动和ODBC驱动)【详细教程】

1.打开MySQL的官网&#xff0c;选择下载(Download) MySQL[这里是图片001]https://www.mysql.com/cn/ 2.往下划点击MySQL Community(GPL)Downloads 3.要下载MySQL的jar包的选择Connector/J 4.进入后&#xff0c;根据自己的需求选择相应的版本 5.下载完成后&#xff0c;进行解压…...

idea 2019.3常用插件

idea 2019.3常用插件 文档 idea 2019.3常用插件idea 2023.3.7常用插件 idea 2019.3常用插件 插件名称插件版本说明1AceJump3.5.9AceJump允许您快速将插入符号导航到编辑器中可见的任何位置。只需按“ctrl&#xff1b;”&#xff0c;键入一个字符&#xff0c;然后在Ace Jump…...

对CSS了解哪些?

CSS&#xff08;Cascading Style Sheets&#xff0c;层叠样式表&#xff09;是用来描述HTML文档外观和布局的语言。以下是对CSS的常见了解范围&#xff1a; 1. CSS 基础 选择器&#xff1a;如通用选择器 (*)、类型选择器、类选择器 (.class)、ID选择器 (#id)、后代选择器、伪类…...

TikTok账户安全指南:如何取消两步验证?

TikTok账户安全指南&#xff1a;如何取消两步验证&#xff1f; 在这个数字化的时代&#xff0c;保护我们的在线账户安全变得尤为重要。TikTok&#xff0c;作为全球流行的社交媒体平台&#xff0c;其账户安全更是不容忽视。两步验证作为一种增强账户安全性的措施&#xff0c;虽…...

从零到一:构建现代 React 应用的完整指南

1. create-react-app (CRA) 简介: create-react-app 是官方推荐的 React 项目脚手架工具,提供了一个开箱即用的开发环境,帮助开发者快速启动 React 应用。它会自动配置 Webpack、Babel、ESLint 等工具,让你专注于开发而不需要手动配置工具链。 特点: 零配置:CRA 自动配…...

【Python爬虫(26)】Python爬虫进阶:数据清洗与预处理的魔法秘籍

【Python爬虫】专栏简介&#xff1a;本专栏是 Python 爬虫领域的集大成之作&#xff0c;共 100 章节。从 Python 基础语法、爬虫入门知识讲起&#xff0c;深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑&#xff0c;覆盖网页、图片、音频等各类数据爬取&#xff…...

机器学习数学基础:28.卡方检验

卡方检验教程 一、引言 在统计学的广阔领域中&#xff0c;卡方检验&#xff08;Chi - Square Test&#xff09;宛如一把锐利的手术刀&#xff0c;能够精准剖析数据背后隐藏的关系与模式。它主要用于两大核心任务&#xff1a;一是深入分析两个及两个以上分类变量之间错综复杂的…...

【工具插件类教学】实现运行时2D物体交互的利器Runtime2DTransformInteractor

目录 ​编辑 1. 插件核心功能 1.1 基础变换操作 1.2 高级特性 2. 安装与配置 2.1 导入插件 2.2 配置控制器参数 2.3 为物体添加交互功能 3. 使用示例 3.1 基础操作演示 3.2 多选与批量操作 3.3 自定义光标与外观 4. 高级配置技巧 4.1 动态调整包围框控件尺寸 4.…...

回调处理器

文章目录 什么是回调处理器回调处理器的工作流程回调处理器的使用自定义链组件中的回调 内置回调处理器自定义回调处理器 在编程领域中&#xff0c;回调是一个非常重要的概念。简而言之&#xff0c;回调是一种特殊的函数或方法&#xff0c;它可以被传递给另一个函数作为参数&am…...

Redis-03高级篇中-多级缓存:

说明&#xff1a; 分布式缓存和多级缓存的视频&#xff0c;与springcloud高级篇redis的一模一样。这里就不在重复学习了&#xff0c;如果后面用到关于redis的配置&#xff0c;直接到springcloud模块安装的redis中学习即可。 多级缓存 0.学习目标 1.什么是多级缓存 传统的缓…...

Spring Boot ShardingJDBC分库分表(草稿)

ShardingJDBC分库分表 1.Maven 引用 <dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>4.1.1</version></dependency><dependency><…...

Jenkins 环境搭建---基于 Docker

前期准备 提前安装jdk、maven、nodeJs&#xff08;如果需要的话&#xff09; 创建 jenkins 环境目录&#xff0c;用来当做挂载卷 /data/jenkins/ 一&#xff1a;拉取 Jenkins 镜像 docker pull jenkins/jenkins:lts 二&#xff1a;设置 Jenkins挂载目录 mkdir -p ~/jen…...

如何在自定义组件中使用v-model实现双向绑定

在 Vue 2 中&#xff0c;v-model 是双向数据绑定的语法糖&#xff0c;它默认将 value 作为 prop 传入组件&#xff0c;并通过监听 input 事件来更新父组件的数据。若要在自定义组件中实现 v-model 的双向绑定&#xff0c;需遵循以下步骤&#xff1a; 1. 基本实现&#xff1a;va…...

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_pool_t 类型

ngx_pool_t 定义在 src/core/ngx_core.h typedef struct ngx_pool_s ngx_pool_t; ngx_pool_s 定义在 src/core/ngx_palloc.h struct ngx_pool_s {ngx_pool_data_t d;size_t max;ngx_pool_t *current;ngx_chain_t *chain;ng…...

如何在 ubuntu 上使用 Clash 与 docker 开启代理拉起

如何在 ubuntu 上使用 Clash https://github.com/doreamon-design/clash/releases上面是clash 的地址 clash_2.0.24_linux_386.tar.gz 下载 386 的 如果你的电脑是inter tar -xzvf clash_2.0.24_linux_386.tar.gz 启动 ./clash 然后会在电脑上生成一个config的文件 /home/xxx/…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...