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

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问题。随着硬件和编程语言的发展,我们可以期待更多的工具和库来简化无锁编程。

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

相关文章:

C++中的无锁编程

引言 在当今多核处理器普及的时代&#xff0c;并发编程已成为高性能应用程序开发的关键技术。传统的基于锁的同步机制虽然使用简单&#xff0c;但往往会带来性能瓶颈和死锁风险。无锁编程&#xff08;Lock-Free Programming&#xff09;作为一种先进的并发编程范式&#xff0c…...

7. 机器人记录数据集(具身智能机器人套件)

1. 树莓派启动机器人 conda activate lerobotpython lerobot/scripts/control_robot.py \--robot.typelekiwi \--control.typeremote_robot2. huggingface平台配置 huggingface官网 注册登录申请token&#xff08;要有写权限&#xff09;安装客户端 # 安装 pip install -U …...

设计模式 + java8方法引用 实现任意表的过滤器

会用到下面2个依赖&#xff0c;原因是在今天的案例中&#xff0c;我想在我代码中使用上Entity::getFieldName 这种形式 LambdaQueryWrapper<ApplicationDashboard> queryWrapper new LambdaQueryWrapper<>(); queryWrapper.eq(ApplicationDashboard::getAppCode,…...

分布式锁—5.Redisson的读写锁二

大纲 1.Redisson读写锁RedissonReadWriteLock概述 2.读锁RedissonReadLock的获取读锁逻辑 3.写锁RedissonWriteLock的获取写锁逻辑 4.读锁RedissonReadLock的读读不互斥逻辑 5.RedissonReadLock和RedissonWriteLock的读写互斥逻辑 6.写锁RedissonWriteLock的写写互斥逻辑…...

【C++设计模式】第七篇:桥接模式(Bridge)

注意&#xff1a;复现代码时&#xff0c;确保 VS2022 使用 C17/20 标准以支持现代特性。 抽象与实现的解耦之道 1. 模式定义与用途​​ 核心思想​ ​桥接模式&#xff1a;将抽象部分与实现部分分离&#xff0c;使二者可以独立变化。​关键用途&#xff1a; ​1.拆分复杂继承…...

Html常用代码

Html常用代码 文章目录 Html常用代码1-常用的Html代码1-Html模板 2-快速部署Live-Server1-Windows系统步骤 1&#xff1a;安装 Node.js步骤 2&#xff1a;安装 live - server步骤 3&#xff1a;使用 live - server 运行本地项目 2-Mac系统步骤 1&#xff1a;安装 Node.js步骤 2…...

c++中的一些控制符

控制符在<iomanip>头文件里 一、设置显示小数精度 &#xff1a;setprecision() float A3.1234&#xff1b; 默认有效位为6位&#xff0c;steprecision(3)→设置有效位为3位 【3.12】 可以与fixed搭配用&#xff0c;cout<<fixed<<setprecision(3)<&l…...

Go语言里面的堆跟栈 + new 和 make + 内存逃逸 + 闭包

在 Go 语言中&#xff0c;堆&#xff08;Heap&#xff09;和栈&#xff08;Stack&#xff09;是内存管理中的两个重要概念&#xff0c;它们在内存分配、数据存储和使用场景等方面存在明显差异。 栈&#xff08;Stack&#xff09; 栈是一种具有后进先出&#xff08;LIFO&#…...

蓝桥备赛(11)- 数据结构、算法与STL

一、数据结构 1.1 什么是数据结构&#xff1f; 在计算机科学中&#xff0c;数据结构是一种 数据组织、管理和存储的格式。它是相互之间存在一种 或多种特定关系的数据元素的集合。 ---> 通俗点&#xff0c;数据结构就是数据的组织形式 &#xff0c; 研究数据是用什么方…...

react 19版中路由react-router-dom v7版的使用

路由的安装&#xff1a; npm install react-router-dom在src目录下建一个router文件夹 在router文件夹里面建一个index.tsx index.tsx内容&#xff1a; import React from react; import {BrowserRouter as Router,Routes,Route,Link } from react-router-dom; import ManuLi…...

WPS工具栏添加Mathtype加载项

问题描述&#xff1a; 分别安装好WPS和MathType之后&#xff0c;WPS工具栏没直接显示MathType工具&#xff0c;或者是前期使用正常&#xff0c;由于WPS更新之后MathType工具消失&#xff0c;如下图 解决办法 将文件“MathType Commands 2016.dotm”和“MathPage.wll”从Matht…...

Tauri+React+Ant Design跨平台开发环境搭建指南

TauriReactAnt Design跨平台开发环境搭建指南 一、环境配置与工具链搭建 1.1 基础环境准备 必备组件&#xff1a; Rust工具链&#xff08;v1.77&#xff09;&#xff1a; curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh Node.js LTS&#xff08;v20.11.1&a…...

PDF转JPG(并去除多余的白边)

首先&#xff0c;手动下载一个软件&#xff08;poppler for Windows&#xff09;&#xff0c;下载地址&#xff1a;https://github.com/oschwartz10612/poppler-windows/releases/tag/v24.08.0-0 否则会出现以下错误&#xff1a; PDFInfoNotInstalledError: Unable to get pag…...

std::string的模拟实现

目录 string的构造函数 无参数的构造函数 根据字符串初始化 用n个ch字符初始化 用一个字符串的前n个初始化 拷贝构造 用另一个string对象的pos位置向后len的长度初始化 [ ]解引用重载 迭代器的实现 非const版本 const版本 扩容reserve和resize reserve resize p…...

wordpress自定the_category的输出结构

通过WordPress的过滤器the_category来自定义输出内容。方法很简单&#xff0c;但是很实用。以下是一个示例代码&#xff1a; function custom_the_category($thelist, $separator , $parents ) {// 获取当前文章的所有分类$categories get_the_category();if (empty($categ…...

doris: Oracle

Apache Doris JDBC Catalog 支持通过标准 JDBC 接口连接 Oracle 数据库。本文档介绍如何配置 Oracle 数据库连接。 使用须知​ 要连接到 Oracle 数据库&#xff0c;您需要 Oracle 19c, 18c, 12c, 11g 或 10g。 Oracle 数据库的 JDBC 驱动程序&#xff0c;您可以从 Maven 仓库…...

mysql中什么机制保证宕机数据恢复

MySQL 通过多种机制来保证在宕机或意外崩溃时数据的完整性和可恢复性。这些机制主要包括 事务日志、崩溃恢复 和 数据持久化 等。以下是 MySQL 中保证数据恢复的核心机制: 1. 事务日志(Transaction Log) 事务日志是 MySQL 实现数据恢复的核心机制之一,主要包括 Redo Log(…...

前端面试技术性场景题

87.场景面试之大数运算&#xff1a;超过js中number最大值的数怎么处理 在 JavaScript 中&#xff0c;Number.MAX_SAFE_INTEGER&#xff08;即 2^53 - 1&#xff0c;即 9007199254740991&#xff09;是能被安全表示的最大整数。超过此值时&#xff0c;普通的 Number 类型会出现…...

解决CentOS 8.5被恶意扫描的问题

CentOS 8 官方仓库已停止维护(EOL),导致一些常用依赖包如fail2ban 无法正常安装。 完整解决方案: 一、问题根源 CentOS 8 官方仓库已停更:2021 年底 CentOS 8 停止维护,默认仓库的包可能无法满足依赖关系。EPEL 仓库兼容性:EPEL 仓库可能未适配 CentOS 8.5 的旧版本依赖…...

探秘基带算法:从原理到5G时代的通信变革【四】Polar 编解码(二)

文章目录 2.3.3 极化编码巴氏参数与信道可靠性比特混合生成矩阵编码举例 2.3.4 极化译码最小单元译码串行抵消译码&#xff08;SC译码&#xff09;算法SCL译码算法 2.3.5 总结**Polar 码的优势****Polar 码的主要问题****Polar 码的应用前景** 2.3.6 **参考文档** 本博客为系列…...

机器学习准备工作

机器学习准备工作 机器学习概述常见算法动手实践 深度学习基础框架应用领域 数学基础线性代数概率论和统计学微积分 编程基础Python基础数据处理工具 项目实战入门1. Scikit-learn 示例项目2. TensorFlow/Keras 入门项目3. Kaggle 入门竞赛 进阶1. PyTorch 官方教程2. MMDetect…...

汽车智能钥匙中PKE低频天线的作用

PKE&#xff08;Passive Keyless Entry&#xff09;即被动式无钥匙进入系统&#xff0c;汽车智能钥匙中PKE低频天线在现代汽车的智能功能和安全保障方面发挥着关键作用&#xff0c;以下是其具体作用&#xff1a; 信号交互与身份认证 低频信号接收&#xff1a;当车主靠近车辆时…...

Codepen和tailwindcss 进行UI布局展示

html <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>设备管理仪表盘</title><script src"https://cdn.tailw…...

准备好了数据集之后,如何在ubuntu22.04上训练一个yolov8模型。

在Ubuntu 22.04上训练YOLOv8模型的步骤如下&#xff1a; 1. 安装依赖 首先&#xff0c;确保系统已安装Python和必要的库。 sudo apt update sudo apt install python3-pip python3-venv2. 创建虚拟环境 创建并激活虚拟环境&#xff1a; python3 -m venv yolov8_env source…...

集合框架、Collection、list、ArrayList、Set、HashSet和LinkedHashSet、判断两个对象是否相等

DAY7.1 Java核心基础 集合框架 Java 中很重要的一个知识点&#xff0c;实际开发中使用的频录较高&#xff0c;Java 程序中必备的模块 集合就是长度可以改变&#xff0c;可以保存任意数据类型的动态数组 最上层是一组接口&#xff0c;接下来是接口的实现类&#xff0c;第三层…...

宝塔 Linux 计划任务中添加运行项目网站PHP任务-定时任务

一、指定php版运行&#xff0c; cd /www/wwwroot/www.xxx.com/ && /www/server/php/56/bin/php think timedtasks start >> /tmp/timedtasks.log 2>&1 二、不指定php版 cd /www/wwwroot/www.xxx.com/ && php think timedtasks start >> …...

JDK ZOOKEEPER KAFKA安装

JDK17下载安装 mkdir -p /usr/local/develop cd /usr/local/develop 将下载的包上传服务器指定路径 解压文件 tar -zxvf jdk-17.0.14_linux-x64_bin.tar.gz -C /usr/local/develop/ 修改文件夹名 mv /usr/local/develop/jdk-17.0.14 /usr/local/develop/java17 配置环境变量…...

c++雅兰亭库 (yalantinglibs) 介绍及使用(序列化、json和结构体转换、协程

c雅兰亭库 (yalantinglibs) 介绍及使用(序列化、json和结构体转换、协程)-CSDN博客 雅兰亭库(yalantinglibs)介绍 雅兰亭库&#xff0c;名字很优雅&#xff0c;也很强大。它是阿里开源的一个现代C基础工具库的集合, 现在包括 struct_pack, struct_json, struct_xml, struct_yam…...

深度融合,智领未来丨zAIoT 全面集成 DeepSeek,助力企业迎接数据智能新时代

前言 Introduction 在数字化浪潮汹涌澎湃的当下&#xff0c;数据智能成为企业破局与创新的关键驱动力。zAIoT 作为云和恩墨面向 AIData 时代推出的数据智能平台软件&#xff0c;凭借其全面且强大的“采存算用”一体化功能体系&#xff0c;正在为航空航天、工业制造等领域和态势…...

类和对象—多态—案例2—制作饮品

案例描述&#xff1a; 制作饮品的大致流程为&#xff1a;煮水-冲泡-倒入杯中-加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作产品基类&#xff0c;提供子类制作咖啡和茶叶 思路解析&#xff1a; 1. 定义抽象基类 - 创建 AbstractDrinking 抽象类&#xff0c;该类…...