C++中的无锁编程

引言

在当今多核处理器普及的时代,并发编程已成为高性能应用程序开发的关键技术。传统的基于锁的同步机制虽然使用简单,但往往会带来性能瓶颈和死锁风险。无锁编程(Lock-Free Programming)作为一种先进的并发编程范式,通过避免使用互斥锁,能够显著提高并发程序的性能和可扩展性。本文将深入探讨C++中的无锁编程技术,包括其基本概念、实现方法、常见模式以及实际应用中的注意事项。

无锁编程的基本概念

无锁编程是一种不使用互斥锁而实现线程同步的技术。在无锁算法中,即使某个线程的执行被挂起,其他线程仍然能够继续执行而不会被阻塞。这种特性使得无锁算法在高并发环境下具有显著的性能优势。

无锁、无等待与无障碍

在讨论无锁编程时,我们通常会涉及以下几个概念:

  • 无障碍(Obstruction-Free):如果一个线程在其他线程都暂停的情况下能够在有限步骤内完成操作,则该算法是无障碍的。
  • 无锁(Lock-Free):如果系统中总有线程能够取得进展,则该算法是无锁的。无锁算法保证系统整体的进展,但不保证每个线程都能取得进展。
  • 无等待(Wait-Free):如果每个线程都能在有限步骤内完成操作,则该算法是无等待的。无等待是最强的保证,它确保每个线程都能取得进展。

无锁编程的核心在于使用原子操作和内存序来协调线程间的交互,而不是依赖传统的锁机制。

C++原子操作

C++11引入了<atomic>库,提供了对原子操作的支持,这是实现无锁编程的基础。

原子类型

std::atomic是一个模板类,可以将基本类型包装为原子类型:

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>std::atomic<int> counter(0);void increment() {for (int i = 0; i < 1000; ++i) {counter++;  // 原子递增操作}
}int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.push_back(std::thread(increment));}for (auto& t : threads) {t.join();}std::cout << "Final counter value: " << counter << std::endl;return 0;
}

原子操作函数

除了基本的原子类型外,C++还提供了一系列原子操作函数:

  • std::atomic_load:原子读取
  • std::atomic_store:原子存储
  • std::atomic_exchange:原子交换
  • std::atomic_compare_exchange_weak/std::atomic_compare_exchange_strong:比较并交换(CAS操作)

CAS(Compare-And-Swap)操作是无锁编程中最常用的基本操作之一:

bool cas_example(std::atomic<int>& target, int expected, int desired) {return target.compare_exchange_strong(expected, desired);
}

内存序

在多处理器系统中,内存操作的顺序可能会因为编译器优化和处理器重排序而改变。C++11引入了内存序(Memory Ordering)的概念,允许程序员指定原子操作之间的顺序关系。

内存序类型

C++提供了六种内存序选项:

  • memory_order_relaxed:最宽松的内存序,只保证操作的原子性,不提供同步或顺序保证。
  • memory_order_consume:读操作依赖于特定的写操作。
  • memory_order_acquire:读操作,获取当前线程之前的所有写操作的结果。
  • memory_order_release:写操作,释放当前线程之前的所有写操作的结果。
  • memory_order_acq_rel:同时具有acquire和release语义。
  • memory_order_seq_cst:最严格的内存序,提供全序关系。

以下是一个使用不同内存序的示例:

#include <atomic>
#include <thread>
#include <iostream>std::atomic<bool> ready(false);
std::atomic<int> data(0);void producer() {data.store(42, std::memory_order_relaxed);ready.store(true, std::memory_order_release);
}void consumer() {while (!ready.load(std::memory_order_acquire)) {// 自旋等待}int value = data.load(std::memory_order_relaxed);std::cout << "Read value: " << value << std::endl;
}int main() {std::thread t1(producer);std::thread t2(consumer);t1.join();t2.join();return 0;
}

在这个例子中,memory_order_releasememory_order_acquire配对使用,确保consumer线程能够看到producer线程写入的数据。

无锁数据结构

基于原子操作和适当的内存序,我们可以实现各种无锁数据结构。

无锁队列

以下是一个简化版的无锁单生产者单消费者队列实现:

template<typename T>
class LockFreeQueue {
private:struct Node {T data;std::atomic<Node*> next;Node(const T& value) : data(value), next(nullptr) {}};std::atomic<Node*> head;std::atomic<Node*> tail;public:LockFreeQueue() {Node* dummy = new Node(T());head.store(dummy);tail.store(dummy);}~LockFreeQueue() {while (Node* node = head.load()) {head.store(node->next);delete node;}}void enqueue(const T& value) {Node* new_node = new Node(value);Node* old_tail = tail.exchange(new_node, std::memory_order_acq_rel);old_tail->next.store(new_node, std::memory_order_release);}bool dequeue(T& result) {Node* current_head = head.load(std::memory_order_acquire);Node* next = current_head->next.load(std::memory_order_acquire);if (!next) {return false;  // 队列为空}result = next->data;head.store(next, std::memory_order_release);delete current_head;return true;}
};

无锁栈

以下是一个基于CAS操作的无锁栈实现:

template<typename T>
class LockFreeStack {
private:struct Node {T data;Node* next;Node(const T& value) : data(value), next(nullptr) {}};std::atomic<Node*> head;public:LockFreeStack() : head(nullptr) {}~LockFreeStack() {while (Node* node = head.load()) {head.store(node->next);delete node;}}void push(const T& value) {Node* new_node = new Node(value);Node* old_head = head.load(std::memory_order_relaxed);do {new_node->next = old_head;} while (!head.compare_exchange_weak(old_head, new_node, std::memory_order_release,std::memory_order_relaxed));}bool pop(T& result) {Node* old_head = head.load(std::memory_order_acquire);do {if (!old_head) {return false;  // 栈为空}} while (!head.compare_exchange_weak(old_head, old_head->next,std::memory_order_release,std::memory_order_relaxed));result = old_head->data;delete old_head;return true;}
};

ABA问题及其解决方案

在无锁编程中,一个常见的问题是ABA问题:一个值从A变为B,再变回A,可能会导致CAS操作误认为该值没有被修改过。

ABA问题示例

考虑以下场景:

  1. 线程1读取一个指针值A
  2. 线程1被挂起
  3. 线程2将指针从A改为B,然后又改回A
  4. 线程1恢复执行,发现指针值仍为A,误认为没有发生变化

解决方案:标记指针

一种常见的解决方案是使用标记指针(Tagged Pointer)或版本计数器:

template<typename T>
class TaggedPointer {
private:struct TaggedPtr {T* ptr;uint64_t tag;};std::atomic<TaggedPtr> ptr;public:TaggedPointer(T* p = nullptr) {TaggedPtr tp = {p, 0};ptr.store(tp);}bool compareAndSwap(T* expected, T* desired) {TaggedPtr current = ptr.load();if (current.ptr != expected) {return false;}TaggedPtr newValue = {desired, current.tag + 1};return ptr.compare_exchange_strong(current, newValue);}T* get() {return ptr.load().ptr;}
};

C++11提供了std::atomic<std::shared_ptr<T>>,可以直接用于解决ABA问题,但其性能可能不如手动实现的标记指针。

无锁编程的实践建议

1. 谨慎选择内存序

选择合适的内存序对于无锁算法的正确性和性能至关重要。过于严格的内存序会导致性能下降,而过于宽松的内存序可能会导致程序错误。

2. 考虑内存管理

在无锁编程中,内存管理是一个复杂的问题。当一个线程释放一个对象时,其他线程可能仍在使用该对象。解决这个问题的方法包括:

  • 引用计数
  • 危险指针(Hazard Pointers)
  • 纪元计数(Epoch-based reclamation)
  • 读拷贝更新(Read-Copy-Update,RCU)

3. 全面测试

无锁算法的正确性难以验证,因此需要进行全面的测试,包括压力测试和并发测试。

4. 避免过度使用

无锁编程不是万能的。在许多情况下,简单的锁机制可能更适合,特别是当并发度不高或性能不是关键因素时。

性能对比与分析

为了展示无锁编程的性能优势,我们对比了基于锁的队列和无锁队列在不同线程数下的性能:

// 性能测试代码
#include <chrono>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
#include <iostream>// 基于锁的队列
template<typename T>
class LockedQueue {
private:std::queue<T> q;std::mutex mtx;public:void enqueue(const T& value) {std::lock_guard<std::mutex> lock(mtx);q.push(value);}bool dequeue(T& result) {std::lock_guard<std::mutex> lock(mtx);if (q.empty()) {return false;}result = q.front();q.pop();return true;}
};// 测试函数
template<typename Queue>
void benchmark(Queue& q, int num_threads, int operations_per_thread) {auto start = std::chrono::high_resolution_clock::now();std::vector<std::thread> threads;for (int i = 0; i < num_threads; ++i) {threads.push_back(std::thread([&q, operations_per_thread]() {for (int j = 0; j < operations_per_thread; ++j) {q.enqueue(j);int result;q.dequeue(result);}}));}for (auto& t : threads) {t.join();}auto end = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);std::cout << "Time taken with " << num_threads << " threads: " << duration.count() << " ms" << std::endl;
}

测试结果表明,在高并发场景下,无锁队列的性能优势显著,特别是当线程数接近或超过CPU核心数时。

总结与展望

无锁编程作为一种高级并发编程技术,能够在高并发场景下提供显著的性能优势。C++11及后续标准通过提供原子操作和内存序模型,为无锁编程提供了坚实的基础。

然而,无锁编程也面临着诸多挑战,包括复杂的内存管理、难以验证的正确性以及潜在的ABA问题。随着硬件和编程语言的发展,我们可以期待更多的工具和库来简化无锁编程。

在实际应用中,应当根据具体场景选择合适的并发策略,无锁编程并非适用于所有情况。对于并发度不高或对性能要求不严格的场景,传统的基于锁的同步机制可能是更简单、更可靠的选择。

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

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

相关文章

FastGPT 引申:借鉴 FastGPT 基于MySQL + ES 实现知识库(含表结构以及核心代码)

文章目录 FastGPT 引申&#xff1a;借鉴 FastGPT 基于MySQL ES 实现知识库&#xff08;含表结构以及核心代码&#xff09;一、整体思路二、存储结构2.1 MySQL 表结构(1) knowledge_base_dataset(2) knowledge_base_data(3) knowledge_base_index(4) ai_kb_relation 2.2 Elasti…

Python学习(十四)pandas库入门手册

目录 一、安装与导入二、核心数据结构2.1 Series 类型&#xff08;一维数组&#xff09;2.2 DataFrame 类型&#xff08;二维数组&#xff09; 三、数据读取与写入3.1 读取 CSV 和 Excel 文件3.2 写入数据 四、数据清洗与处理4.1 处理缺失值4.2 数据筛选4.3 数据排序 五、数据分…

【Python 数据结构 4.单向链表】

目录 一、单向链表的基本概念 1.单向链表的概念 2.单向链表的元素插入 元素插入的步骤 3.单向链表的元素删除 元素删除的步骤 4.单向链表的元素查找 元素查找的步骤 5.单向链表的元素索引 元素索引的步骤 6.单向链表的元素修改 元素修改的步骤 二、Python中的单向链表 ​编辑 三…

第1章:项目概述与环境搭建

第1章&#xff1a;项目概述与环境搭建 学习目标 了解YunChangAction灵感记录应用的整体架构和功能掌握SwiftUI开发环境的配置方法创建项目基础结构并理解文件组织方式实现应用的启动屏幕和基本主题设置 理论知识讲解 灵感记录应用概述 灵感记录应用是一种专门设计用来帮助…

2025.3.3总结

周一这天&#xff0c;我约了绩效教练&#xff0c;主要想了解专业类绩效的考核方式以及想知道如何拿到一个更好的绩效。其他的岗位并不是很清楚&#xff0c;但是专业类的岗位&#xff0c;目前采取绝对考核&#xff0c;管理层和专家岗采取相对考核&#xff0c;有末尾淘汰。 通过…

FastGPT 源码:基于 LLM 实现 Rerank (含Prompt)

文章目录 基于 LLM 实现 Rerank函数定义预期输出实现说明使用建议完整 Prompt 基于 LLM 实现 Rerank 下边通过设计 Prompt 让 LLM 实现重排序的功能。 函数定义 class LLMReranker:def __init__(self, llm_client):self.llm llm_clientdef rerank(self, query: str, docume…

LeetCode 1745.分割回文串 IV:动态规划(用III或II能直接秒)

【LetMeFly】1745.分割回文串 IV&#xff1a;动态规划&#xff08;用III或II能直接秒&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/palindrome-partitioning-iv/ 给你一个字符串 s &#xff0c;如果可以将它分割成三个 非空 回文子字符串&#xff0c;…

25年3月5日

1.思维导图 2.不太会 #include "head.h" int main(int argc, const char *argv[]) {int fdopen("../xiaoxin.bmp","O_RDONLY");if(fd-1)printf("open error");//大小struct stat st;if(stat("…

全球首创!微软发布医疗AI助手,终结手写病历时代

今天凌晨&#xff0c;微软发布了医疗界首个用于临床工作流程的AI助手Microsoft Dragon Copilot。 Dragon Copilot是基于语音文本的混合架构&#xff0c;能够将医生的语音或临床口述内容实时转换为文本。例如&#xff0c;医生可以通过语音输入患者的病历信息、医嘱或诊断结果&a…

[自动驾驶-传感器融合] 多激光雷达的外参标定

文章目录 引言外参标定原理ICP匹配示例参考文献 引言 多激光雷达系统通常用于自动驾驶或机器人&#xff0c;每个雷达的位置和姿态不同&#xff0c;需要将它们的数据统一到同一个坐标系下。多激光雷达外参标定的核心目标是通过计算不同雷达坐标系之间的刚性变换关系&#xff08…

Blazor-路由模板(下)

路由约束 类型约束 我们这里使用{id:int}限制路由&#xff0c;id为int类型&#xff0c;并且路由参数 id 对应的 Id 属性也必须是 int 类型。我们试试能否正常访问 page "/demoPage/{id:int}" <h3>demoPage</h3> <h2>路由参数Id&#xff1a;Id&l…

多线程-JUC源码

简介 JUC的核心是AQS&#xff0c;大部分锁都是基于AQS扩展出来的&#xff0c;这里先结合可重入锁和AQS&#xff0c;做一个讲解&#xff0c;其它的锁的实现方式也几乎类似 ReentrantLock和AQS AQS的基本结构 AQS&#xff0c;AbstractQueuedSynchronizer&#xff0c;抽象队列…

通过多线程获取RV1126的AAC码流

目录 一RV1126多线程获取音频编码AAC码流的流程 1.1AI模块的初始化并使能 1.2AENC模块的初始化 ​​​​​​​1.3绑定AI模块和AENC模块 ​​​​​​​1.4多线程获取每一帧AAC码流 ​​​​​​​1.5每个AAC码流添加ADTSHeader头部 ​​​​​​​1.6写入具体每一帧AAC的…

JVM常用概念之对象初始化的成本

在JVM常用概念之新对象实例化博客中我讲到了对象的实例化&#xff0c;主要包含分配&#xff08;TLAB&#xff09;、系统初始化、用户初始化&#xff0c;而我在JVM常用概念之线程本地分配缓冲区&#xff08;ThreadLocal Allocation Buffer&#xff0c;TLAB&#xff09;博客中也讲…

java后端开发day27--常用API(二)正则表达式爬虫

&#xff08;以下内容全部来自上述课程&#xff09; 1.正则表达式&#xff08;regex&#xff09; 可以校验字符串是否满足一定的规则&#xff0c;并用来校验数据格式的合法性。 1.作用 校验字符串是否满足规则在一段文本中查找满足要求的内容 2.内容定义 ps&#xff1a;一…

AI---DevOps常备工具(‌AI-Integrated DevOps Essential Tools)

AI---DevOps常备工具 技术领域正在迅速发展&#xff0c;随着我们步入 2025 年&#xff0c;有一点是明确的&#xff1a;人工智能&#xff08;AI&#xff09;不再只是一个流行词&#xff0c;它是每个 DevOps 工程师都需要掌握的工具。随着云环境的复杂性增加、对更快部署的需求以…

Pytorch中的主要函数

目录 一、torch.manual_seed(seed)二、torch.cuda.manual_seed(seed)三、torch.rand(*size, outNone, dtypeNone, layouttorch.strided, deviceNone, requires_gradFalse)四、给大家写一个常用的自动选择电脑cuda 或者cpu 的小技巧五、torch.version.cuda&#xff1b;torch.bac…

Spring Boot中对接Twilio以实现发送验证码和验证短信码

Twilio介绍 Twilio是一家提供云通信服务的公司&#xff0c;旨在帮助开发者和企业通过简单的API实现各种通信功能。以下是Twilio的一些主要特点和服务介绍&#xff1a; 核心功能 短信服务&#xff08;SMS&#xff09;&#xff1a;允许用户通过API发送和接收短信&#xff0c;支…

VSCode详细安装步骤,适用于 Windows/macOS/Linux 系统

以下是 Visual Studio Code (VSCode) 的详细安装步骤&#xff0c;适用于 Windows/macOS/Linux 系统&#xff1a; VSCode 的详细安装步骤 一、Windows 系统安装1. 下载安装包2. 运行安装程序3. 验证安装 二、macOS 系统安装1. 方法一&#xff1a;官网下载安装包2. 方法二&#x…

基于PyTorch的深度学习3——基于autograd的反向传播

反向传播&#xff0c;可以理解为函数关系的反向传播。