原子操作CAS(Compare-And-Swap)和锁

news2025/7/14 17:18:01

目录

原子操作

优缺点

        互斥锁(Mutex)

        自旋锁(Spin Lock)

原子性

        单核单CPU

        多核多CPU

         存储体系结构

        缓存一致性

        写传播(Write Propagation)

事务串行化(Transaction Serialization)

MESI 一致性协议

原子序

 共享指针(shared_ptr)


原子操作

        定义:原子操作指的是在执行过程中不可被中断的操作,该操作要么完整地执行完毕,要么完全不执行,不存在执行到一半的中间状态。 在多线程或者多进程环境里,原子操作能够保证在同一时刻仅有一个线程或者进程可以对共享资源进行访问和修改,从而避免数据竞争和不一致的问题。

优缺点

        高性能:由于 CAS 是无锁算法,避免了锁的竞争和上下文切换带来的性能开销,因此在高并发场景下具有较高的性能。

        避免死锁:传统的锁机制在使用不当的情况下可能会导致死锁问题,而 CAS 操作不存在死锁的风险。


        ABA 问题:CAS 操作在比较内存位置的值时,只关注值是否相等,而不关心值的变化过程。如果一个值从 A 变为 B,再从 B 变回 A,CAS 操作会认为值没有发生变化,从而继续进行更新操作。这种情况被称为 ABA 问题。

        自旋开销:在 CAS 操作失败时,通常会采用自旋的方式不断重试,直到操作成功。如果竞争非常激烈,自旋会消耗大量的 CPU 资源,降低系统性能。

        互斥锁(Mutex)

        定义:互斥锁(Mutual Exclusion Lock)是一种用于保护共享资源的同步原语,确保同一时间只有一个线程可以访问该资源。当一个线程获取到互斥锁时,其他线程如果尝试获取该锁,会被阻塞,直到持有锁的线程释放锁。

        工作原理

  • 线程尝试获取互斥锁。如果锁当前未被持有,线程会成功获取锁并继续执行后续操作。
  • 如果锁已经被其他线程持有,尝试获取锁的线程会被放入等待队列并进入阻塞状态,此时线程会放弃 CPU 资源,操作系统会调度其他线程执行。
  • 当持有锁的线程完成对共享资源的操作后,会释放锁。此时,操作系统会从等待队列中唤醒一个等待的线程,让其获取锁并继续执行。
  • 优点
    • 避免了多个线程同时访问共享资源,从而防止数据竞争和不一致的问题。
    • 当锁的持有时间较长时,阻塞线程可以避免 CPU 资源的浪费,因为线程在阻塞期间不会占用 CPU 时间。
  • 缺点
    • 线程阻塞和唤醒的过程涉及到操作系统的调度,会带来一定的上下文切换开销,尤其是在锁的竞争非常激烈的情况下,这种开销可能会比较大。
    • 实现相对复杂,需要操作系统的支持。

        应用场景:适用于锁的持有时间较长、线程竞争较为激烈的场景,例如对数据库的读写操作、文件的访问


        自旋锁(Spin Lock)

        定义:自旋锁是一种忙等待的锁机制。当一个线程尝试获取自旋锁时,如果锁已经被其他线程持有,该线程不会进入阻塞状态,而是会不断地循环检查锁的状态,直到锁被释放。

        工作原理

  • 线程尝试获取自旋锁。如果锁当前未被持有,线程会成功获取锁并继续执行后续操作。
  • 如果锁已经被其他线程持有,尝试获取锁的线程会进入一个循环,不断地检查锁的状态,在这个过程中,线程会一直占用 CPU 资源。
  • 当持有锁的线程释放锁后,自旋的线程会检测到锁的状态变化,从而成功获取锁并继续执行。
  • 优点
    • 没有线程阻塞和唤醒的上下文切换开销,因此在锁的持有时间较短的情况下,性能较高。
    • 实现相对简单,不需要操作系统的复杂调度。
  • 缺点
    • 如果锁的持有时间较长,自旋的线程会一直占用 CPU 资源,导致 CPU 资源的浪费,降低系统的整体性能。
    • 在多处理器系统中,如果多个线程同时自旋等待同一个锁,会导致多个 CPU 核心都处于忙等待状态,进一步降低系统效率。

        应用场景:适用于锁的持有时间非常短、线程竞争不激烈的场景,例如在一些内核代码中对临界区的保护。

时间判断:临界资源访问时间和锁切换所花费的时间相比

原子性

        原子性可以保证在同一时刻只有一个线程或进程能够对共享资源进行特定操作,避免了多个线程或进程同时修改共享资源而引发的错误。

        单核单CPU
  • 只需要保证操作指令不被打断(调用机制)
  • 底层自旋锁
  • 屏蔽中断
        多核多CPU
  • 除了不被打断
  • 在0x86,lock指令锁总线,避免所有内存的访问
  • 现在lock指令只需阻止其他核心对相关内存空间的访问
         存储体系结构

                三级缓存cache:为了解决CPU运算速度与内存访问速度不匹配的问题

        写回策略(write-back)

        1.写

        

        2.读

 基于写会策略会产生缓存不一致的问题

        缓存一致性

        问题产生的原因:CPU是多核的,会访问到未同步的数据

        解决方法:

        写传播(Write Propagation)

        定义:写传播是解决缓存一致性问题的一种基本策略,其核心目标是确保在多处理器系统中,当一个处理器对共享数据进行写操作时,这个写操作的结果能够被其他处理器感知到,进而保证所有处理器的缓存中该数据副本的一致性。

        工作原理:当一个处理器对其缓存中的数据进行写操作时,需要将这个写操作传播到其他处理器的缓存或者主存中。具体的传播方式可以分为写直达(Write-Through)和写回(Write-Back)两种:

  • 写直达:处理器在对缓存进行写操作的同时,也会将数据写回到主存中。这样可以保证主存中的数据始终是最新的,但缺点是每次写操作都需要访问主存,会增加写操作的延迟。
  • 写回:处理器只对缓存进行写操作,只有当缓存行被替换时,才将修改后的数据写回到主存中。这种方式减少了对主存的访问次数,提高了写操作的性能,但会增加缓存一致性协议的复杂度。
    事务串行化(Transaction Serialization)

        定义:事务串行化是数据库管理系统中用于保证事务并发执行正确性的一种隔离级别。事务是一组不可分割的数据库操作序列,事务串行化要求所有事务按照某种顺序依次执行,就好像这些事务是一个接一个串行执行的一样,从而避免了并发执行可能带来的数据不一致问题。

        工作原理:在事务串行化隔离级别下,数据库系统会对事务进行排序,并按照这个顺序依次执行。为了实现这一点,数据库系统通常会使用锁机制来保证同一时间只有一个事务可以访问和修改共享数据。当一个事务获得了所需的锁后,其他事务必须等待该事务释放锁后才能继续执行。

        优缺点

  • 优点:可以保证事务的一致性和隔离性,避免了脏读、不可重复读和幻读等并发问题。
  • 缺点:并发性能较低,因为事务必须串行执行,会导致大量的事务等待时间,降低了系统的吞吐量。
    MESI 一致性协议

        定义:MESI 协议是一种广泛使用的缓存一致性协议,它是基于总线监听机制实现的。MESI 协议定义了缓存行的四种状态:Modified(已修改)、Exclusive(独占)、Shared(共享)和 Invalid(无效),通过这四种状态的转换来保证缓存一致性。

        四种状态

  • Modified(已修改):表示该缓存行中的数据已经被修改,并且与主存中的数据不一致。此时,该处理器是唯一拥有该数据最新副本的处理器,并且负责将修改后的数据写回主存。
  • Exclusive(独占):表示该缓存行中的数据只被一个处理器的缓存持有,并且与主存中的数据一致。如果该处理器对该数据进行写操作,它可以直接将状态转换为 Modified 状态,而不需要与其他处理器进行额外的通信。
  • Shared(共享):表示该缓存行中的数据与主存中的数据一致,并且可能被多个处理器的缓存共享。当一个处理器对共享数据进行写操作时,需要先将其他处理器缓存中的该数据副本标记为 Invalid 状态。
  • Invalid(无效):表示该缓存行中的数据是无效的,需要从主存或其他处理器的缓存中重新获取。

        工作原理:每个处理器的缓存控制器会监听系统总线,当其他处理器对共享数据进行操作时,会通过总线广播相应的消息。缓存控制器根据接收到的消息和当前缓存行的状态,进行状态转换和数据更新。

内存序

        为什么会有内存序的问题?

  •         编译器优化重排
  •         cpu指令优化重排

         内存序规定了什么?

  •         规定了多个线程访问同一个内存地址时的语义
  •         某个线程对内存地址的更新何时能被其他线程看见       
  •         某个线程对内存地址访问附近可以做怎样的优化

         6种不同的内存序

  • memory_order_relaxed:松散内存序,只用来保证对原子对 象的操作是原子的,在不需要保证顺序时使用;

 

  • memory_order_release:释放操作,在写入某原子对象时, 当前线程的任何前面的读写操作都不允许重排到这个操作的后面 去,并且当前线程的所有内存写入都在对同一个原子对象进行获 取的其他线程可见;

 

  • memory_order_acquire:获得操作,在读取某原子对象时,当前线程的任何后面的读写操作都不允许重排到这个操作的前面去,并且其他线程在对同一个原子对象释放之前的所有内存写入都在当前线程可见;

  •  memory_order_consume:不建议使用;
  • memory_order_acq_rel:获得释放操作,一个读‐修改‐写操作 同时具有获得语义和释放语义,即它前后的任何读写操作都不允 许重排,并且其他线程在对同一个原子对象释放之前的所有内存 写入都在当前线程可见,当前线程的所有内存写入都在对同一个 原子对象进行获取的其他线程可见;
  • memory_order_seq_cst:顺序一致性语义,对于读操作相当 于获得,对于写操作相当于释放,对于读‐修改‐写操作相当于获 得释放,是所有原子操作的默认内存序,并且会对所有使用此模 型的原子操作建立一个全局顺序,保证了多个原子变量的操作在 所有线程里观察到的操作顺序相同,当然它是最慢的同步模型。
在多线程情况下是否保证可见性顺序性
memory_order_relaxedxx
memory_order_acquire
memory_order_release    √    
memory_order_consume--
memory_order_acq_rel
memory_order_seq_cst

 共享指针(shared_ptr)

   std::shared_ptr 采用引用计数的方式来管理对象。当一个 std::shared_ptr 被创建并指向一个对象时,该对象的引用计数会被初始化为 1。每当有新的 std::shared_ptr 指向同一个对象时,引用计数就会加 1;而当一个 std::shared_ptr 被销毁(例如离开其作用域)或者被重新赋值指向其他对象时,引用计数会减 1。当引用计数变为 0 时,意味着没有任何 std::shared_ptr 再指向该对象,此时 std::shared_ptr 会自动释放该对象所占用的内存。

        我们用原子变量来实现shared_ptr种引用技术的实现

#pragma once

#include <atomic>

// 自定义的 shared_ptr 模板类,用于管理动态分配的对象
template <typename T>
class shared_ptr {
public:
    // 默认构造函数,初始化 ptr_ 为 nullptr,ref_count_ 也为 nullptr
    // 表示这个 shared_ptr 不管理任何对象
    shared_ptr() : ptr_(nullptr), ref_count_(nullptr) {}

    // 带参数的构造函数,接受一个指向 T 类型对象的指针
    // 使用 explicit 关键字防止隐式转换
    // 如果传入的指针不为空,创建一个新的原子引用计数对象并初始化为 1
    // 否则,ref_count_ 为 nullptr
    explicit shared_ptr(T* ptr) : ptr_(ptr), ref_count_(ptr ? new std::atomic<std::size_t>(1) : nullptr) {}

    // 析构函数,调用 release 方法来释放管理的对象和引用计数
    ~shared_ptr() {
        release();
    }

    // 拷贝构造函数,用于创建一个新的 shared_ptr 并共享另一个 shared_ptr 管理的对象
    // 复制 ptr_ 和 ref_count_
    // 如果 ref_count_ 不为空,将引用计数加 1
    shared_ptr(const shared_ptr<T>& other) : ptr_(other.ptr_), ref_count_(other.ref_count_) {
        if (ref_count_) {
            // 使用 std::memory_order_relaxed 进行原子操作,只保证原子性,不保证顺序
            ref_count_->fetch_add(1, std::memory_order_relaxed);
        }
    }

    // 拷贝赋值运算符,用于将一个 shared_ptr 的值赋给另一个 shared_ptr
    // 首先检查是否是自我赋值,如果不是则释放当前管理的对象和引用计数
    // 然后复制 ptr_ 和 ref_count_,如果 ref_count_ 不为空,将引用计数加 1
    shared_ptr<T>& operator=(const shared_ptr<T>& other) {
        if (this != &other) {
            release();
            ptr_ = other.ptr_;
            ref_count_ = other.ref_count_;
            if (ref_count_) {
                ref_count_->fetch_add(1, std::memory_order_relaxed);
            }
        }
        return *this;
    }

    // noexcept 表示这个函数不会抛出异常,编译器可以生成更高效的代码
    // 移动构造函数,用于将另一个 shared_ptr 的所有权转移到当前 shared_ptr
    // 复制 ptr_ 和 ref_count_,并将原 shared_ptr 的 ptr_ 和 ref_count_ 置为 nullptr
    shared_ptr<T>(shared_ptr<T>&& other) noexcept : ptr_(other.ptr_), ref_count_(other.ref_count_) {
        other.ptr_ = nullptr;
        other.ref_count_ = nullptr;
    }

    // 移动赋值运算符,用于将另一个 shared_ptr 的所有权转移到当前 shared_ptr
    // 首先检查是否是自我赋值,如果不是则释放当前管理的对象和引用计数
    // 然后复制 ptr_ 和 ref_count_,并将原 shared_ptr 的 ptr_ 和 ref_count_ 置为 nullptr
    shared_ptr<T>& operator=(shared_ptr<T>&& other) noexcept {
        if (this != &other) {
            release();
            ptr_ = other.ptr_;
            ref_count_ = other.ref_count_;
            other.ptr_ = nullptr;
            other.ref_count_ = nullptr;
        }
        return *this;
    }

    // 解引用运算符重载,返回管理对象的引用
    // 允许像使用普通指针一样使用 shared_ptr 进行解引用操作
    T& operator*() const {
        return *ptr_;
    }

    // 箭头运算符重载,返回管理对象的指针
    // 允许像使用普通指针一样使用 shared_ptr 调用对象的成员函数
    T* operator->() const {
        return ptr_;
    }

    // 获取当前 shared_ptr 管理对象的引用计数
    // 如果 ref_count_ 不为空,使用 std::memory_order_acquire 加载引用计数的值
    // 否则返回 0
    std::size_t use_count() const {
        return ref_count_ ? ref_count_->load(std::memory_order_acquire) : 0;
    }

    // 获取管理对象的原始指针
    T* get() const {
        return ptr_;
    }

    // 重置 shared_ptr 管理的对象
    // 首先释放当前管理的对象和引用计数
    // 然后将 ptr_ 指向新的对象,如果新对象不为空,创建一个新的引用计数对象并初始化为 1
    void reset(T * p = nullptr) {
        release();
        ptr_ = p;
        ref_count_ = p ? new std::atomic<std::size_t>(1) : nullptr;
    }

private:
    // 释放管理的对象和引用计数
    // 使用 std::memory_order_acq_rel 进行原子操作,保证操作的原子性和顺序
    // 如果引用计数减 1 后变为 1,表示这是最后一个引用,释放对象和引用计数
    void release() {
        if (ref_count_ && ref_count_->fetch_sub(1, std::memory_order_acq_rel) == 1) {
            delete ptr_;
            delete ref_count_;
        }
    }
    // 指向管理对象的指针
    T* ptr_;
    // 指向原子引用计数对象的指针
    std::atomic<std::size_t>* ref_count_;
};

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

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

相关文章

【WPF】 在WebView2使用echart显示数据

文章目录 前言一、NuGet安装WebView2二、代码部分1.xaml中引入webview22.编写html3.在WebView2中加载html4.调用js方法为Echarts赋值 总结 前言 为了实现数据的三维效果&#xff0c;所以需要使用Echarts&#xff0c;但如何在WPF中使用Echarts呢&#xff1f; 一、NuGet安装WebV…

OpenCV 图像拼接

一、图像拼接的介绍 图像拼接是一种将多幅具有部分重叠内容的图像合并成一幅完整、无缝且具有更广阔视野或更高分辨率图像的技术。其目的是通过整合多个局部图像来获取更全面、更具信息价值的图像内容。 二、图像拼接的原理 图像拼接的核心目标是将多幅有重叠区域的图像进行准…

数学建模AI智能体(4.16大更新)

别的不说就说下面这几点&#xff0c;年初内卷到现在&#xff0c;就现阶段AI水平&#xff0c;卷出了我比较满意的作品&#xff0c;这里分享给各位同学&#xff0c;让你们少走弯路&#xff1a; 1.轻松辅导学生 2.帮助学习 3.突破知识壁垒&#xff0c;缩短与大佬的差距 4.打破…

Linux》》bash 、sh 执行脚本

通常使用shell去运行脚本&#xff0c;两种方法 》bash xxx.sh 或 bash “xxx.sh” 、sh xxx.sh 或 sh “xxx.sh” 》bash -c “cmd string” 引号不能省略 我们知道 -c 的意思是 command&#xff0c;所以 bash -c 或 sh -c 后面应该跟一个 command。

如何用“AI敏捷教练“破解Scrum项目中的“伪迭代“困局?

一、什么是“伪迭代”&#xff1f; “伪迭代”是指团队表面上采用Scrum框架&#xff0c;但实际运作仍沿用瀑布模式的现象。例如&#xff1a;迭代初期开发人员集中编码、末期测试人员突击测试&#xff0c;导致资源分配不均&#xff1b;需求拆分粗糙&#xff0c;团队无法在固定时…

使用 vxe-table 来格式化任意的金额格式,支持导出与复制单元格格式到 excel

使用 vxe-table 来格式化任意的金额格式&#xff0c;支持导出与复制单元格格式到 excel 查看官网&#xff1a;https://vxetable.cn gitbub&#xff1a;https://github.com/x-extends/vxe-table gitee&#xff1a;https://gitee.com/x-extends/vxe-table 安装 npm install vx…

金币捕鱼类手游《海洋管家》源码结构与系统分层解析

在休闲互动类移动应用开发中&#xff0c;捕鱼类项目因玩法成熟、逻辑清晰而成为不少开发者接触多端架构与模块化管理的重要起点。本文以一款名为《海洋管家》的项目源码为样例&#xff0c;简要解析其整体结构与主要功能模块&#xff0c;供有类似项目需求或系统学习目的的开发者…

Go语言实现OAuth 2.0认证服务器

文章目录 1. 项目概述1.1 OAuth2 流程 2. OAuth 2.0 Storage接口解析2.1 基础方法2.2 客户端管理相关方法2.3 授权码相关方法2.4 访问令牌相关方法2.5 刷新令牌相关方法 2.6 方法调用时序2.7 关键注意点3. MySQL存储实现原理3.1 数据库设计3.2 核心实现 4. OAuth 2.0授权码流程…

【2025年认证杯数学中国数学建模网络挑战赛】C题 数据预处理与问题一二求解

目录 【2025年认证杯数学建模挑战赛】C题数据预处理与问题一求解三、数据预处理及分析3.1 数据可视化3.2 滑动窗口相关系数统计与动态置信区间耦合分析模型3.3 耦合关系分析结果 四、问题一代码数据预处理问题一 【2025年认证杯数学建模挑战赛】C题 数据预处理与问题一求解 三…

2025年最新Web安全(面试题)

活动发起人小虚竹 想对你说&#xff1a; 这是一个以写作博客为目的的创作活动&#xff0c;旨在鼓励大学生博主们挖掘自己的创作潜能&#xff0c;展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴&#xff0c;那么&#xff0c;快来参加吧&#xff01…

开源微调混合推理模型:cogito-v1-preview-qwen-32B

一、模型概述 1.1 模型特点 Cogito v1-preview-qwen-32B 是一款基于指令微调的生成式语言模型&#xff08;LLM&#xff09;&#xff0c;具有以下特点&#xff1a; 支持直接回答&#xff08;标准模式&#xff09;和自我反思后再回答&#xff08;推理模式&#xff09;。使用 I…

Golang|Channel 相关用法理解

文章目录 用 channel 作为并发小容器channel 的遍历channel 导致的死锁问题用 channel 传递信号用 channel 并行处理文件用channel 限制接口的并发请求量用 channel 限制协程的总数量 用 channel 作为并发小容器 注意这里的 ok 如果为 false&#xff0c;表示此时不仅channel为空…

C++ - #命名空间 #输入、输出 #缺省参数 #函数重载 #引用 # const 引用 #inline #nullptr

文章目录 前言 一、实现C版本的hello world 二、命名空间 1、namespace 的价值 2、namespace 的定义 (1.域会影响一个编译器编译语法时的查找规则 (2、域会影响生命周期 (3、命名空间域只能定义在全局 (4、编译器会自动合并相同命名空间中的内容 (5、C标准库放在命名…

JSON处理工具/框架的常见类型及详解,以Java语言为例

以下是JSON处理工具/框架的常见类型及详解&#xff0c;以Java语言为例&#xff1a; 一、主流JSON处理工具对比 Jackson&#xff08;推荐&#xff09; 特点&#xff1a;高性能、功能丰富&#xff0c;支持注解&#xff08;如JsonProperty&#xff09;、树形模型&#xff08;Json…

中间件--ClickHouse-1--基础介绍(列式存储,MPP架构,分布式计算,SQL支持,向量化执行,亿万级数据秒级查询)

1、概述 ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。它由俄罗斯的互联网巨头Yandex为解决其内部数据分析需求而开发&#xff0c;并于2016年开源。专为大规模数据分析&#xff0c;实时数据分析和复杂查询设计&#xff0c;具有高性能、实时数据和可扩展性等…

Java中的经典排序算法:插入排序、希尔排序、选择排序、堆排序与冒泡排序(如果想知道Java中有关插入排序、希尔排序、选择排序、堆排序与冒泡排序的知识点,那么只看这一篇就足够了!)

前言&#xff1a;排序算法是计算机科学中的基础问题之一&#xff0c;它在数据处理、搜索算法以及各种优化问题中占有重要地位&#xff0c;本文将详细介绍几种经典的排序算法&#xff1a;插入排序、选择排序、堆排序和冒泡排序。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解…

K8S+Prometheus+Consul+alertWebhook实现全链路服务自动发现与监控、告警配置实战

系列文章目录 k8s服务注册到consul prometheus监控标签 文章目录 系列文章目录前言一、环境二、Prometheus部署1.下载2.部署3.验证 三、kube-prometheus添加自定义监控项1.准备yaml文件2.创建新的secret并应用到prometheus3.将yaml文件应用到集群4.重启prometheus-k8s pod5.访…

uniapp-商城-25-顶部模块高度计算

计算高度&#xff1a; 使用computed进行顶部模块的计算。 总高度&#xff1a;bartotalHeight log 介绍--收款码这一条目 也就是上一章节的title的高度计算 bodybarheight。 在该组件中&#xff1a; js部分的代码&#xff1a; 包含了导出的名字&#xff1a; shop-head…

非关系型数据库(NoSQL)与 关系型数据库(RDBMS)的比较

非关系型数据库&#xff08;NoSQL&#xff09;与 关系型数据库&#xff08;RDBMS&#xff09;的比较 一、引言二、非关系型数据库&#xff08;NoSQL&#xff09;2.1 优势 三、关系型数据库&#xff08;RDBMS&#xff09;3.1 优势 四、结论 &#x1f496;The Begin&#x1f496;…

蓝桥杯2024国B数星星

小明正在一棵树上数星星&#xff0c;这棵树有 n 个结点 1,2,⋯,n。他定义树上的一个子图 G 是一颗星星&#xff0c;当且仅当 G 同时满足&#xff1a; G 是一棵树。G 中存在某个结点&#xff0c;其度数为 ∣VG​∣−1。其中 ∣VG​∣ 表示这个子图含有的结点数。 两颗星星不相…