C++无锁数据结构:CAS(Compare-and-Swap)

在高并发场景下,传统锁机制带来的线程阻塞和上下文切换开销成为性能瓶颈。无锁数据结构通过原子操作实现线程安全,避免了锁的使用,成为高性能系统的关键技术。本文将深入探讨C++中基于CAS(Compare-and-Swap)的无锁数据结构实现原理、应用场景及实战技巧。

一、CAS原理解析

1.1 什么是CAS?

CAS是一种原子操作,包含三个参数:内存地址(V)、旧值(A)和新值(B)。操作逻辑如下:

  • 如果内存地址V中的值等于旧值A,则将该位置的值更新为新值B
  • 否则返回当前内存中的实际值

CAS操作通常由CPU硬件直接支持,是无锁编程的核心原语。

1.2 C++中的CAS实现

C++11在<atomic>头文件中提供了原子操作支持,其中compare_exchange_weakcompare_exchange_strong对应CAS操作:

#include <atomic>template<typename T>
bool atomic_compare_and_swap(std::atomic<T>& atom, T& expected, T desired) {return atom.compare_exchange_weak(expected, desired);
}
  • compare_exchange_weak:可能会虚假失败(即使值相等也可能返回false),但性能更高
  • compare_exchange_strong:保证不会虚假失败,但可能需要更多次重试

1.3 CAS的ABA问题

CAS操作存在ABA问题:当值从A变为B再变回A时,CAS会误认为值没有变化。解决方法通常是使用带版本号的原子变量:

template<typename T>
struct VersionedValue {T value;uint64_t version;bool operator==(const VersionedValue& other) const {return value == other.value && version == other.version;}
};std::atomic<VersionedValue<int>> atom;// 更新操作
void update(int new_value) {VersionedValue<int> expected = atom.load();VersionedValue<int> desired;do {desired.value = new_value;desired.version = expected.version + 1;} while (!atom.compare_exchange_weak(expected, desired));
}

二、基于CAS的无锁栈实现

2.1 单链表节点结构

template<typename T>
struct Node {T data;Node* next;Node(const T& value) : data(value), next(nullptr) {}
};

2.2 无锁栈核心实现

template<typename T>
class LockFreeStack {
private:std::atomic<Node<T>*> head;public:LockFreeStack() : head(nullptr) {}// 入栈操作void push(const T& value) {Node<T>* new_node = new Node<T>(value);new_node->next = head.load();while (!head.compare_exchange_weak(new_node->next, new_node)) {// CAS失败,说明有其他线程修改了head// 重新加载head并尝试}}// 出栈操作bool pop(T& value) {Node<T>* old_head = head.load();while (old_head != nullptr) {// 尝试将head指向下一个节点if (head.compare_exchange_weak(old_head, old_head->next)) {value = old_head->data;delete old_head;return true;}}return false; // 栈为空}
};

2.3 ABA问题处理

上述实现存在ABA问题,改进版使用带标记的指针:

template<typename T>
class LockFreeStack {
private:struct TaggedPointer {Node<T>* ptr;uint64_t tag;bool operator==(const TaggedPointer& other) const {return ptr == other.ptr && tag == other.tag;}};std::atomic<TaggedPointer> head;public:void push(const T& value) {Node<T>* new_node = new Node<T>(value);TaggedPointer old_head = head.load();do {new_node->next = old_head.ptr;TaggedPointer new_head = {new_node, old_head.tag + 1};} while (!head.compare_exchange_weak(old_head, new_head));}bool pop(T& value) {TaggedPointer old_head = head.load();while (old_head.ptr != nullptr) {TaggedPointer new_head = {old_head.ptr->next, old_head.tag + 1};if (head.compare_exchange_weak(old_head, new_head)) {value = old_head.ptr->data;delete old_head.ptr;return true;}}return false;}
};

三、无锁队列实现

3.1 Michael-Scott无锁队列

template<typename T>
class LockFreeQueue {
private:struct Node {T data;std::atomic<Node*> next;Node() : next(nullptr) {}Node(const T& value) : data(value), next(nullptr) {}};std::atomic<Node*> head;std::atomic<Node*> tail;public:LockFreeQueue() {Node* dummy = new Node();head.store(dummy);tail.store(dummy);}~LockFreeQueue() {T value;while (pop(value));delete head.load();}// 入队操作void enqueue(const T& value) {Node* new_node = new Node(value);Node* old_tail = tail.load();while (true) {// 尝试将新节点连接到队尾if (old_tail->next.compare_exchange_weak(nullptr, new_node)) {// 成功连接,尝试更新tail指针tail.compare_exchange_strong(old_tail, new_node);return;} else {// 连接失败,说明有其他线程已经更新了tailtail.compare_exchange_strong(old_tail, old_tail->next);old_tail = tail.load();}}}// 出队操作bool dequeue(T& value) {Node* old_head = head.load();while (true) {Node* next = old_head->next.load();if (next == nullptr) {return false; // 队列为空}// 尝试更新head指针if (head.compare_exchange_weak(old_head, next)) {value = next->data;delete old_head; // 释放原头节点(dummy或前一个节点)return true;}}}
};

四、CAS无锁算法的性能考量

4.1 优势

  • 无阻塞:线程不会因竞争而阻塞,减少上下文切换开销
  • 细粒度并发:允许多个线程同时操作不同部分的数据结构
  • 避免死锁:无需获取锁,从根本上消除死锁风险

4.2 劣势

  • 高竞争下性能下降:大量CAS失败导致频繁重试
  • 实现复杂度高:需要处理ABA问题、内存回收等复杂问题
  • 调试困难:非确定性的执行顺序增加调试难度

4.3 适用场景

  • 高并发低竞争:线程间冲突较少的场景
  • 实时系统:不允许长时间阻塞的场景
  • 高性能核心组件:如网络框架、数据库引擎

五、内存回收方案

无锁数据结构的一个关键挑战是安全地回收内存,避免"悬垂指针"问题。常见方案有:

5.1 Hazard Pointer

// 简化版Hazard Pointer实现
template<typename T>
class HazardPointer {
private:static constexpr int MAX_THREADS = 64;std::atomic<void*> hazard_pointers[MAX_THREADS];std::atomic<int> thread_count;thread_local static int thread_id;public:void* get_hazard_pointer() {if (thread_id == -1) {thread_id = thread_count.fetch_add(1);}return hazard_pointers[thread_id].load();}void set_hazard_pointer(void* ptr) {hazard_pointers[thread_id].store(ptr);}void clear_hazard_pointer() {hazard_pointers[thread_id].store(nullptr);}bool can_reclaim(Node<T>* node) {for (int i = 0; i < thread_count; ++i) {if (hazard_pointers[i].load() == node) {return false;}}return true;}
};

5.2 延迟删除

// 简化版延迟删除实现
template<typename T>
class LockFreeStack {
private:std::atomic<Node<T>*> head;std::atomic<int> delete_count;std::vector<Node<T>*> to_be_deleted;const int BATCH_SIZE = 100;public:bool pop(T& value) {Node<T>* old_head = head.load();while (old_head != nullptr) {if (head.compare_exchange_weak(old_head, old_head->next)) {value = old_head->data;// 延迟删除to_be_deleted.push_back(old_head);if (++delete_count >= BATCH_SIZE) {perform_deletion();}return true;}}return false;}void perform_deletion() {std::vector<Node<T>*> local;local.swap(to_be_deleted);delete_count = 0;for (auto node : local) {delete node;}}
};

六、总结与最佳实践

  1. 优先使用标准库实现:C++标准库提供了许多无锁容器,如std::atomicstd::queue的并发版本
  2. 谨慎使用无锁结构:仅在性能关键且锁竞争激烈的场景使用
  3. 解决ABA问题:使用带版本号的指针或Hazard Pointer
  4. 注意内存回收:避免使用裸指针,采用安全的内存回收机制
  5. 充分测试:无锁算法的正确性依赖于原子操作的顺序,需进行充分测试

无锁数据结构是一把双刃剑,正确使用能带来显著性能提升,但实现难度较大。开发者需根据具体场景权衡利弊,选择合适的实现方案。随着硬件和编程语言的发展,无锁编程将在高性能领域发挥越来越重要的作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87748.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87748.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数字图像处理】

数字图像处理 绪论1. 数字图像处理基本概念2. 数字图像处理系统的组成3. 数字图像处理技术研究的内容4. 数字图像处理技术的应用领域5. 图像处理技术涉及的学科领域 图像处理基础1. 电磁波谱与可见光谱2. 人眼的亮度视觉特性3. 图像的表示4. 空间分辨率和灰度级分辨率5. 像素间…

linux chrome浏览器打不开了

报错信息 通过terminal执行google-chrome [12714:12714:0706/223620.723519:ERROR:chrome/browser/process_singleton_posix.cc:358] The profile appears to be in use by another Google Chrome process (54949) on another computer (192.168.0.17). Chrome has locked t…

Python:模块

一、Python模块基础概念 1. 什么是Python模块&#xff1f; 在 Python 中&#xff0c;模块&#xff08;Module&#xff09; 是一个包含 Python 代码的文件&#xff08;扩展名为 .py&#xff09;&#xff0c;用于组织代码、实现功能复用和命名空间管理。模块可以包含变量、函数…

C 语言指针与作用域详解

一、指针基础概念 &#xff08;一&#xff09;指针的本质 指针是 C 语言中一个重要的概念&#xff0c;其本质是内存地址。在计算机内存中&#xff0c;每个字节都有唯一的编号&#xff0c;这个编号就是我们所说的内存地址&#xff0c;而指针变量就是用于存储这些内存地址的变量…

解锁阿里云ACK:开启Kubernetes容器化应用新时代

引言&#xff1a;云原生时代下的 ACK 在当今数字化飞速发展的时代&#xff0c;云原生技术正以前所未有的速度改变着软件开发和部署的格局。随着企业对应用敏捷性、弹性扩展以及成本优化的需求日益增长&#xff0c;云原生已成为众多企业实现数字化转型的关键路径。在云原生的技…

【C++基础】内存管理四重奏:malloc/free vs new/delete - 面试高频考点与真题解析

在 C/C 编程中&#xff0c;内存管理是核心基础技能&#xff0c;而malloc/free和new/delete作为两套内存分配释放机制&#xff0c;是面试中高频出现的考点。 一、内存管理的 "双生花"&#xff1a;基础概念解析 1.1 malloc/free&#xff1a;C 语言的内存管家 malloc全…

Dify+Ollama+QwQ:3步本地部署,开启AI搜索新篇章

如何来评价本地化部署的价值与优势分析&#xff1a; 成本优化与隐私保障 自定义搜索插件&#xff0c;告别信息过载 一键生成报告、分析&#xff0c;效率翻倍&#xff01; 接下来我们就尝试跟随来部署本地的价值所在! 1&#xff1a;安装Ollama & 部署QwQ模型 1.1 安装O…

FAISS 简介及其与 GPT 的对接(RAG)

什么是 FAISS&#xff1f; FAISS (Facebook AI Similarity Search) 是 Facebook AI 团队开发的一个高效的相似性搜索和密集向量聚类的库。它主要用于&#xff1a; 大规模向量相似性搜索高维向量最近邻检索向量聚类 https://github.com/facebookresearch/faissFAISS 特别适合处理…

【Apache Doris 深度实战:从 MPP 架构到实时分析,解锁三大数据模型的性能优化秘籍】

一、安装部署 安装教程&#xff1a;GitHub地址 Doc文档&#xff1a;Apache Doris 简介 - Apache Doris 二、功能及作用 Apache Doris 是一款基于MPP 架构的高性能、实时分析型数据库。它以高效、简单和统一的特性著称&#xff0c;能够在亚秒级的时间内返回海量数据的查询结果…

MySQL主从复制与读写分离概述

前言&#xff1a; 在数据驱动的现代应用中&#xff0c;数据库面临高并发读写与海量存储的双重挑战。单一数据库实例在性能、可用性及扩展性上逐渐成为瓶颈。MySQL主从复制&#xff08;Master-Slave Replication&#xff09;与读写分离&#xff08;Read/Write Splitting&#xf…

数据库-元数据表

1. 什么是元数据表元数据&#xff1a;数据的数据&#xff0c;用以描述数据的信息也是数据&#xff0c;被称为元数据2. 获取元数据的方法MySQL提供了以下三种方法用于获取数据库对象的元数据&#xff1a;show语句从INFORMATION_SCHEMA数据库里查询相关表&#xff08;information…

【STM32】通用定时器PWM

STM32 通用定时器 PWM 输出完全解析&#xff08;以 TIM3_CH1 为例&#xff09; PWM 输出基本原理 PWM&#xff08;Pulse Width Modulation&#xff09;即脉冲宽度调制&#xff0c;是由定时器通过比较 CNT 与 CCR 寄存器实现的。 信号产生原理&#xff1a; ARR 决定周期&#…

python学习打卡:DAY 21 常见的降维算法

知识点回顾&#xff1a; LDA线性判别PCA主成分分析t-sne降维 还有一些其他的降维方式&#xff0c;也就是最重要的词向量的加工&#xff0c;我们未来再说 浙大疏锦行

基于SpringBoot和Leaflet集成在线天气服务的区县当前天气WebGIS实战

目录 前言 一、需求描述 1、功能需求 2、技术实现流程 二、SpringBoot后台实现 1、控制层实现 2、区县数据返回 三、WebGIS前端实现 1、区位信息展示 2、天气信息展示 四、成果展示 1、魔都上海 2、蜀地成都 3、湖南桂东 五、总结 前言 在当今数字化时…

文心开源:文心大模型4.5系列全面开放,AI普惠时代加速到来

一场由4240亿参数模型领衔的开源盛宴&#xff0c;正在重塑中国AI生态的底层逻辑 2025年6月30日&#xff0c;百度如约宣布全面开源其旗舰产品——文心大模型4.5系列。一次性开源10款模型&#xff0c;覆盖从4240亿参数的MoE多模态巨无霸到轻巧的0.3B端侧模型&#xff0c;并同步开…

【运算放大器专题】基础篇

1.1 运算放大器是放大了个寂寞吗&#xff1f;—初识运算放大器 为了解决震荡问题&#xff0c;人为加了一些补偿网络之后导致的高频特性差 1.2欧姆定律和独立源 1正弦2方波3脉冲 电压源是平行于i轴的横线 1.3有伴源和运放缓冲器 有伴指的是有电阻&#xff0c;有伴是坏事&#…

英伟达 jetson nano 从NFS启动,使用英伟达提供的rootfs根文件系统

0、目标 为了方便驱动阶段的开发&#xff0c;并且使用英伟达提供的上层应用&#xff0c;这里希望使jetson nano 从NFS启动&#xff0c;同时使用英伟达提供的rootfs根文件系统。 1、硬件准备 确保jetson nano 板子和开发主机之间使用网线进行连接&#xff08;保持板子和开发主…

广州华锐互动:以创新科技赋能教育,开启沉浸式学习​

在教育领域&#xff0c;广州华锐互动致力于打破传统教学的局限性&#xff0c;为师生们带来全新的沉浸式学习体验。广州华锐互动通过开发 VR 虚拟教学课件&#xff0c;将抽象的知识转化为生动、逼真的虚拟场景&#xff0c;让学生能够身临其境地感受知识的魅力 。比如在历史课上&…

Grok 4 最新技术评测与发布指南

TL;DR&#xff1a;马斯克跳过Grok 3.5直接发布Grok 4&#xff0c;计划在7月4日后上线&#xff0c;专注编程模型优化&#xff0c;这次"极限迭代"能否让马斯克在AI军备竞赛中翻盘&#xff1f; &#x1f4cb; 文章目录 &#x1f680; Grok 4发布概况&#x1f3c6; Grok…

为什么音视频通话需要边缘加速

⏩ 主要原因 ✅ 降低传输延迟 用户与边缘节点之间通常1-2跳即可完成连接&#xff0c;避免跨国、跨运营商长链路传输 保障音视频信令、媒体流快速到达&#xff0c;控制端到端延迟 ✅ 提升弱网环境下的连接稳定性 边缘节点具备链路优化、丢包补偿、转发中继功能 即使在WiFi切…