并发 -- 无锁算法与结构

文章目录

      • 什么是无锁算法
      • 什么是原子变量
      • 什么是CAS操作
      • Compare-And-Swap Weak在哪些情况下会失败
      • 举例说明无锁结构
      • 无锁结构的问题

什么是无锁算法

无锁算法(Lock-Free Algorithm)是一种并发编程技术,旨在实现多线程环境下的高效数据共享,而无需使用传统的锁机制(如互斥锁)。
关键特性或优点:

  1. 无阻塞:至少有一个线程能在有限步骤内完成操作,确保系统整体不会因锁争用而停滞。
  2. 无死锁:由于不使用锁,避免了死锁问题。
  3. 高并发性:适用于多核处理器,多个线程可同时操作共享数据,减少等待时间。

在C++ 可以使用原子变量来实现无锁算法与结构

什么是原子变量

C++的原子变量(Atomic Variable)是通过std::atomic模板类提供的,用于在多线程环境中安全地操作共享数据
模板原形

// Defined in header <atomic>
template< class T >
struct atomic;  // (1)	(since C++11)
template< class U >
struct atomic<U*>; //(2)	(since C++11)
// Defined in header <memory>
template< class U >
struct atomic<std::shared_ptr<U>>;  //(3)	(since C++20)
template< class U >
struct atomic<std::weak_ptr<U>>; // (4)	(since C++20)
Defined in header <stdatomic.h>
#define _Atomic(T) /* see below */  // (5)	(since C++23)

具体参考cppreference atomic

下面是一个使用atomic_flag 实现自旋锁的例子

class SpinLock {std::atomic_flag flag(ATOMIC_FLAG_INIT);
public:void lock(){while(flag.test_and_set(std::memory_and_acquire)){}}void unlock(){flag.clear(std::memory_and_release);}};

其中std::memory_and_releasestd::memory_and_acquire的含义需自行查找内存模型与顺序

使用atomic 操作就是没有显示的使用锁操作,但atomic内部可能是使用锁机制来实现的也可能是使用cpu的CAS操作来实现的
通过atomic::is_lock_free可以查看atomic内部是不是无锁机制实现的

#include <atomic>
#include <iostream>
#include <utility>struct A { int a[100]; };
struct B { int x, y; };
struct C { int x, y, z; };int main()
{std::cout << std::boolalpha<< "std::atomic_char is lock free? "<< std::atomic_char{}.is_lock_free() << '\n'<< "std::atomic_uintmax_t is lock free? "<< std::atomic_uintmax_t{}.is_lock_free() << '\n'<< "std::atomic<A> is lock free? "<< std::atomic<A>{}.is_lock_free() << '\n'<< "std::atomic<B> is lock free? "<< std::atomic<B>{}.is_lock_free() << '\n'<< "std::atomic<C> is lock free? "<< std::atomic<C>{}.is_lock_free() << '\n';
}

是与类型的长度有关的

什么是CAS操作

CAS(Compare-And-Swap,比较并交换)是一种原子操作,用于在多线程环境中实现无锁同步。CAS操作会检查某个内存位置的值是否与预期值匹配,如果匹配,则将其更新为新值;否则,不进行任何操作。无论是否更新,CAS操作都会返回内存位置的原始值。

CAS 操作包含三个操作数:

  1. 内存地址:需要修改的共享变量的地址。
  2. 期望值:当前线程认为该变量应该具有的值。
  3. 新值:如果变量的当前值等于期望值,则将其更新为新值。

CAS 的工作流程:

  1. 读取内存地址中的当前值。
  2. 比较当前值与期望值:
    • 如果相等,则将新值写入内存地址。
    • 如果不相等,则不做任何操作。
  3. 返回操作是否成功(通常返回当前值或布尔值)。

CAS 的硬件支持:

  • x86:CMPXCHG 指令(单字 CAS),CMPXCHG16B 指令(双字 CAS)。
  • ARM:LDREX 和 STREX 指令(加载-存储独占指令)。
  • 其他架构:大多数现代 CPU 都提供了类似的原子指令。

以下是一个使用 CMPXCHG 实现原子操作的 C++ 示例:

#include <iostream>
#include <atomic>// 使用 CMPXCHG 实现原子 CAS 操作
bool atomic_compare_exchange(int* dest, int expected, int new_value) {int result;__asm__ volatile ("lock cmpxchgl %3, %1;"  // lock 前缀确保原子性,cmpxchgl 是 32 位 CMPXCHG: "=a" (result), "+m" (*dest)  // 输出:result = EAX,dest 是内存操作数: "a" (expected), "r" (new_value)  // 输入:EAX = expected,new_value 是寄存器操作数: "memory"  // 告诉编译器内存可能被修改);return result == expected;  // 返回是否成功
}int main() {int shared_value = 10;int expected = 10;int new_value = 20;std::cout << "Initial value: " << shared_value << std::endl;// 尝试原子地将 shared_value 从 expected (10) 更新为 new_value (20)bool success = atomic_compare_exchange(&shared_value, expected, new_value);if (success) {std::cout << "CAS succeeded. New value: " << shared_value << std::endl;} else {std::cout << "CAS failed. Value is still: " << shared_value << std::endl;}return 0;
}

缺点:

  1. ABA 问题:变量的值从 A 变为 B 又变回 A,CAS 无法检测到中间变化。
  2. 忙等待:在高竞争场景下,CAS 可能导致大量重试,浪费 CPU 资源。
  3. 复杂性:实现无锁数据结构需要复杂的逻辑和严格的正确性验证。

CAS操作的变体:

  1. Compare-And-Swap Weak:允许在某些情况下失败(如虚假失败),但性能更高。
  2. Compare-And-Swap Strong:确保只有在值不匹配时才失败,性能较低但更可靠。

Compare-And-Swap Weak在哪些情况下会失败

  1. 当前值与预期值不匹配
  2. 内存问题:
    • 并发竞争 , 如果多个线程同时尝试修改同一个内存地址,CAS Weak 可能会因为竞争而失败。即使当前值与预期值相等,其他线程的干扰也可能导致操作失败。
    • 内存访问冲突:如果内存访问冲突频繁发生,硬件可能会放弃 CAS Weak 操作,导致失败。
    • 在某些弱内存模型(如 ARM 或 PowerPC)中,指令重排序可能会导致 CAS Weak 失败。例如,内存屏障未正确设置时,CAS Weak 可能会观察到不一致的内存状态。
    • 如果系统资源(如缓存、内存带宽)紧张,CAS Weak 可能会因为资源竞争而失败。
    • 缓存一致性协议的影响:在某些多核处理器架构中,缓存一致性协议(如 MESI 协议)可能导致 CAS Weak 失败。例如,当多个核心同时竞争同一内存地址时,缓存行的状态可能会发生变化,从而导致 CAS Weak 失败

Compare-And-Swap Weak 的伪代码实现

template<T>
bool cas_weak(T* dest, T& expected,const T& new_value)
{if (*dest == expected) {if (try_update(dest,new_value)){return true;} else {return false;}} else {expected = *dest;return false}
}

举例说明无锁结构

无锁链表

#include <atomic>
#include <memory>
#include <iostream>template <typename T>
class LockFreeLinkedList {
private:struct Node {std::shared_ptr<T> data; // 存储数据std::atomic<Node*> next; // 指向下一个节点的原子指针Node(T const& value) : data(std::make_shared<T>(value)), next(nullptr) {}};std::atomic<Node*> head; // 链表头指针public:LockFreeLinkedList() : head(nullptr) {}~LockFreeLinkedList() {// 析构时释放所有节点Node* current = head.load();while (current) {Node* next = current->next.load();delete current;current = next;}}// 插入操作void insert(T const& value) {Node* new_node = new Node(value); // 创建新节点new_node->next = head.load();    // 新节点的 next 指向当前头节点// CAS 操作:将 head 从当前值更新为新节点while (!head.compare_exchange_weak(new_node->next, new_node)) {// 如果 CAS 失败,说明 head 已经被其他线程修改,重新尝试}}// 删除操作bool remove(T const& value) {Node* prev = head.load(); // 前驱节点Node* curr = prev;        // 当前节点while (curr) {Node* next = curr->next.load(); // 下一个节点// 如果当前节点的值匹配if (curr->data && *curr->data == value) {// CAS 操作:将前驱节点的 next 从当前节点更新为下一个节点if (prev == curr) {// 如果当前节点是头节点if (head.compare_exchange_weak(curr, next)) {delete curr; // 删除节点return true;}} else {if (prev->next.compare_exchange_weak(curr, next)) {delete curr; // 删除节点return true;}}}// 移动指针prev = curr;curr = next;}return false; // 未找到匹配的节点}// 打印链表内容(用于调试)void print() const {Node* current = head.load();while (current) {if (current->data) {std::cout << *current->data << " -> ";}current = current->next.load();}std::cout << "nullptr" << std::endl;}
};

无锁结构的问题

  1. ABA 问题:在无锁数据结构中,ABA 问题是一个常见的挑战。可以通过使用带有版本号的指针(如 std::atomic<std::shared_ptr>)或双字 CAS(Compare-And-Swap)来解决。
  2. 内存管理:无锁数据结构中的内存管理需要特别小心,因为多个线程可能同时访问和释放内存。通常使用智能指针(如 std::shared_ptr)来管理内存。
  3. 性能测试:无锁数据结构的性能在高并发环境下可能优于基于锁的数据结构,但在低并发环境下可能表现不佳。因此,在实际应用中需要进行充分的性能测试。

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

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

相关文章

考研/保研复试英语问答题库(华工建院)

华南理工大学建筑学院保研/考研 英语复试题库&#xff0c;由华工保研er和学硕笔试第一同学一起整理&#xff0c;覆盖面广&#xff0c;助力考研/保研上岸&#xff01;需要&#x1f447;载可到文章末尾见小&#x1f360;。 以下是主要内容&#xff1a; Part0 复试英语的方法论 Pa…

岳阳市美术馆预约平台(小程序论文源码调试讲解)

第4章 系统设计 一个成功设计的系统在内容上必定是丰富的&#xff0c;在系统外观或系统功能上必定是对用户友好的。所以为了提升系统的价值&#xff0c;吸引更多的访问者访问系统&#xff0c;以及让来访用户可以花费更多时间停留在系统上&#xff0c;则表明该系统设计得比较专…

Python游戏编程之赛车游戏6-3

1 “敌人”汽车类的创建 在创建玩家汽车类之后&#xff0c;接下来创建“敌人”汽车类。“敌人”汽车类与玩家类一样&#xff0c;也是包含两个方法&#xff0c;一个是__init__()&#xff0c;另一个是move()。 1.1 __init__()方法 “敌人”汽车类的__init__()方法代码如图1所示…

TCP/UDP调试工具推荐:Socket通信图解教程

TCP/UDP调试工具推荐&#xff1a;Socket通信图解教程 一、引言二、串口调试流程三、下载链接 SocketTool 调试助手是一款旨在协助程序员和网络管理员进行TCP和UDP协议调试的网络通信工具。TCP作为一种面向连接、可靠的协议&#xff0c;具有诸如连接管理、数据分片与重组、流量和…

神经网络 - 神经元

人工神经元(Artificial Neuron)&#xff0c;简称神经元(Neuron)&#xff0c;是构成神经网络的基本单元&#xff0c;其主要是模拟生物神经元的结构和特性&#xff0c;接收一组输入信号并产生输出。 生物学家在 20 世纪初就发现了生物神经元的结构。一个生物神经元通常具有多个树…

蓝桥杯备考:贪心算法之矩阵消除游戏

这道题是牛客上的一道题&#xff0c;它呢和我们之前的排座位游戏非常之相似&#xff0c;但是&#xff0c;排座位问题选择行和列是不会改变元素的值的&#xff0c;这道题呢每每选一行都会把这行或者这列清零&#xff0c;所以我们的策略就是先用二进制把选择所有行的情况全部枚举…

DeepSeek系统架构的逐层分类拆解分析,从底层基础设施到用户端分发全链路

一、底层基础设施层 1. 硬件服务器集群 算力单元&#xff1a; GPU集群&#xff1a;基于NVIDIA H800/H100 GPU构建&#xff0c;单集群规模超10,000卡&#xff0c;采用NVLink全互联架构实现低延迟通信。国产化支持&#xff1a;适配海光DCU、寒武纪MLU等国产芯片&#xff0c;通过…

ktransformers 上的 DeepSeek-R1 671B open-webui

ktransformers 上的 DeepSeek-R1 671B open-webui 一、下载GGUF模型1. 创建目录2. 魔塔下载 DeepSeek-R1-Q4_K_M3. 安装显卡驱动和cuda4. 显卡 NVIDIA GeForce RTX 4090 二、安装ktransformers1. 安装依赖2. 安装uv工具链3. 下载源码4. 创建python虚拟环境 三、编译ktransforme…

smolagents学习笔记系列(五)Tools-in-depth-guide

这篇文章锁定官网教程中的 Tools-in-depth-guide 章节&#xff0c;主要介绍了如何详细构造自己的Tools&#xff0c;在之前的博文 smolagents学习笔记系列&#xff08;二&#xff09;Agents - Guided tour 中我初步介绍了下如何将一个函数或一个类声明成 smolagents 的工具&…

形式化数学编程在AI医疗中的探索路径分析

一、引言 1.1 研究背景与意义 在数字化时代,形式化数学编程和 AI 形式化医疗作为前沿领域,正逐渐改变着我们的生活和医疗模式。形式化数学编程是一种运用数学逻辑和严格的形式化语言来描述和验证程序的技术,它通过数学的精确性和逻辑性,确保程序的正确性和可靠性。在软件…

C#初级教程(3)——变量与表达式:从基础到实践

一、为什么使用变量 计算机程序本质上是对数据的操作&#xff0c;数字、文字、图片等在计算机中都属于数据。而变量&#xff0c;就是数据在计算机内存中的 “栖息地”。我们可以把变量想象成一个个小盒子&#xff0c;这些盒子能存放各种数据&#xff0c;需要时还能随时取出。 二…

【深度学习神经网络学习笔记(三)】向量化编程

向量化编程 向量化编程前言1、向量化编程2、向量化优势3、正向传播和反向传播 向量化编程 前言 向量化编程是一种利用专门的指令集或并行算法来提高数据处理效率的技术&#xff0c;尤其在科学计算、数据分析和机器学习领域中非常常见。它允许通过一次操作处理整个数组或矩阵的…

海康威视摄像头RTSP使用nginx推流到服务器直播教程

思路&#xff1a; 之前2020年在本科的时候&#xff0c;由于项目的需求需要将海康威视的摄像头使用推流服务器到网页进行直播。这里将自己半个月琢磨出来的步骤给大家发一些。切勿转载&#xff01;&#xff01;&#xff01;&#xff01; 使用网络摄像头中的rtsp协议---------通…

鸿蒙开发深入浅出03(封装通用LazyForEach实现懒加载)

鸿蒙开发深入浅出03&#xff08;封装通用LazyForEach实现懒加载&#xff09; 1、效果展示2、ets/models/BasicDataSource.ets3、ets/models/HomeData.ets4、ets/api/home.ets5、ets/pages/Home.ets6、ets/views/Home/SwiperLayout.ets7、后端代码 1、效果展示 2、ets/models/Ba…

【Rust中级教程】2.8. API设计原则之灵活性(flexible) Pt.4:显式析构函数的问题及3种解决方案

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 说句题外话&#xff0c;这篇文章一共5721个字&#xff0c;是我截至目前写的最长的一篇文章&a…

一周学会Flask3 Python Web开发-Jinja2模板过滤器使用

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 在Jinja2中&#xff0c;过滤器(filter)是一些可以用来修改和过滤变量值的特殊函数&#xff0c;过滤器和变量用一个竖线 | &a…

数据库 安装initializing database不通过

出现一下情况时&#xff1a; 处理方法&#xff1a; 将自己的电脑名称 中文改成英文 即可通过

嵌入式开发:傅里叶变换(5):STM32和Matlab联调验证FFT

目录 1. MATLAB获取 STM32 的原始数据 2. 将数据上传到电脑 3. MATLAB 接收数据并验证 STM32进行傅里叶代码 结果分析 STM32 和 MATLAB 联调是嵌入式开发中常见的工作流程&#xff0c;通常目的是将 STM32 采集的数据或控制信号传输到 MATLAB 中进行实时处理、分析和可视化…

Mobaxterm服务器常用命令(持续更新)

切换文件夹 cd path # for example, cd /gpu03/deeplearning/进入不同GPU ssh mgmt ssh gpu01 ssh gpu03寻找文件位置 find /path -name file_name #for example, find / -name lib #在根目录下搜寻名为lib文件 #for example, find /home/deeplearning -name "lib"…

MFC文件和注册表的操作

MFC文件和注册表的操作 日志、操作配置文件、ini、注册表、音视频的文件存储 Linux下一切皆文件 C/C操作文件 const char* 与 char* const const char* 常量指针&#xff0c;表示指向的内容为常量。指针可以指向其他变量&#xff0c;但是内容不能再变了 char szName[6]&qu…