原型变量、原子操作、原子性、内存序

news2024/11/15 19:55:54

一、原子变量、原子操作

  • 锁竞争:互斥锁条件变量、原子变量、信号量、读写锁、自旋锁。
  • 在高性能基础组件优化的时候,为了进一步提高并发性能,可以使用原子变量。
  • 性能:原子变量 > 自旋锁 > 互斥锁
    • 操作临界资源的时间较长时使用互斥锁(粒度大)
    • 如果只操作单个变量的话,可以使用原子变量(粒度小) 进行优化。
  • 给基础类型或者指针加上一个标记 std::atomic<T> 之后它就是原子变量对这些变量的操作就是原子操作
  • 原子变量是一种多线程编程中常用的同步机制,它能确保对共享变量的操作在执行时不会被其它线程的操作干扰,从而避免竞态条件。
  • 原子变量具备原子性,也就是要么全部完成,要么全部未完成。
  • 为什么会使用到原子变量 ?
    • 通常某一个变量对应的操作,其 CPU 的指令是大于 1 个指令的,在多线程环境下,可能会引发竞态,在这种线程不安全的情况下,我们需要为这个变量单独设置一个锁安全的标记 std::atomic<T>从而实现线程安全
    • 多线程环境下,确保对共享变量的操作在执行时不会被干扰,从而避免竞态条件。
std::atomic<T>
is_lock_free; // 是否支持无锁操作
store(T desired, std::memory_order order); // 用于将特定的值存储到原子对象中
load(std::memory_order order); // 用于获取原子变量的当前值

// 访问和修改包含的值,将包含的值替换并返回它原来的值。如果替换成功,则返回原来的值
exchange(std::atomic<T>* obj, T desired);
/* 
比较一个值和一个期望值是否相等,如果相等则该值替换成一个新值,并返回 true,否则不做任何操作并返回 false。
*/
compare_exchange_weak(T& expected, T val, memory_order success, memory_order failure);
/*
compare_exchange_weak 函数是一个弱化版本的原子操作函数,因为在某些平台上它可能会失败并重试。
如果需要保证严格的原子性,应该使用 compare_exchange_strong 函数
*/
compare_exchange_strong((T& expected, T val, memory_order success, memory_order failure);

fetch_add
fetch_sub
fetch_and
fetch_or
fetch_xor

二、原子性

  • 要么都做要么还没做,不会让其它核心看到执行的一个中间状态
  • 单处理器单核心实现原子性:
    • 只需要保证操作指令不被打断。
      • 屏蔽中断 → 不允许操作指令被打断,不让其它线程看到操作指令的中间状态
      • 底层硬件自旋锁 → 为了实现线程切换。
  • 多处理器或多核心实现原子性:
    • 除了要保证操作指令不被打断,还需要避免其它核心操作相关的内存空间
      • 以往的 0x86 lock 指令,锁总线避免所有内存的访问
      • 现在的 lock 指令,锁总线只阻止其它核心对相关内存空间的访问,也就是只锁住相关内存空间
      • 内存总线:CPU 需要通过内存总线访问内存。
      • 磁盘总线:CPU 需要通过磁盘总线访问磁盘。
  • 存储体系结构
    • 为什么要有 CPU 缓存:为了解决 CPU 运算速度与内存访问速度不匹配的问题,在 CPU 和内存之间设置了一些缓存。
      CPU 和磁盘之间也有一个缓存 → 高速缓冲区(page cache) → 为了解决 CPU 运算速度与磁盘访问速度不匹配的问题。
      
    • L1 和 L2 是核心独有的,L3 是核心共有的,也就是多个核心共用一个 L3
    • CPU 缓存的基本单位:cache line,64 字节(64 B)。每次最少读取的数据空间(不管用户要读的数据空间有多小)。
      • flag:标识缓存中的数据是否可用,存储的是缓存的状态值(MESI)。
      • tag:索引数据是否在缓存中、数据在缓存中的位置。
      • data:具体存储的数据。

在这里插入图片描述

  • 在 CPU 缓存的基础上,CPU 如何读写数据 ? 注意:CPU 缓存中的数据永远比内存中的数据要新
    • 写直达策略
      • 每次写操作既会写到 CPU 缓存中,也会写到内存中,写性能会很低。
    • 写回策略(write-back)(现代 CPU 使用的策略)
      • 尽量避免每次写数据都把数据写到内存中
      • 写操作
        • 先检查是否命中 CPU 缓存,如果命中了就直接写,并标记为脏数据(CPU 缓存中的数据和内存中的数据不一致)
        • 如果没有命中,就需要在 CPU 缓存中找一块区域来存储新数据,也就是定位缓存块
          • 如果有可用的缓存块(还没有存储其它数据的缓存块) 就直接使用。
          • 如果没有可用的缓存块了,则采用 LRU 策略去定位缓存块
            • 该缓存块存储的数据是脏数据,先把这个缓存块中的数据刷到内存中,然后用这个缓存块去存储新数据,并标记为脏数据
            • 该缓存块存储的数据不是脏数据,直接将新数据写入到这个缓存块中,并标记为脏数据
      • 读操作
        • 先检查是否命中 CPU 缓存,如果命中了就直接返回。
        • 如果没有命中,从内存中读取数据并返回,然后定位缓存块去存储该数据。
          • 如果有可用的缓存块就直接使用。
          • 如果没有可用的缓存块了,则采用 LRU 策略去定位缓存块。
            • 该缓存块存储的数据是脏数据,先把这个缓存块中的数据刷到内存中,然后用这个缓存块去存储刚刚从内存中读取的数据,并标记为非脏数据
            • 该缓存块存储的数据不是脏数据,直接将刚刚从内存中读取的数据写入到这个缓存块中,并标记为非脏数据
  • 为什么会有缓存一致性问题 ?
    • CPU 是多核心的。
    • 基于写回策略将会出现缓存不一致的问题。
      • 数据在核心 0 的 CPU 缓存中,但是还没写到内存中;核心 2 的 CPU 缓存中没有该数据,于是会从内存中读取该数据,但是核心 2 从内存中读到的数据与核心 0 的 CPU 缓存中的数据不一致。
  • 如何解决缓存不一致的问题 ?
    • 写传播:总线嗅探(bus snooping)
      • 监听发布者模式:每一个核心都会监听总线上的写事件,当某个核心写数据的时候,会基于总线进行广播,其它的核心读到了这个事件之后,会自动修改自己的数据。
    • 事务的串行化:锁 + lock 指令
      • 多个核心对同一个缓存块进行读写操作的时候,必须要串行执行,否则会带来不确定性。
      • core0 写 i = 10,锁总线,必须等 i = 10 写操作结束后,core1 才能写 i = 20,这样 core2 读到的 i 就一定等于 20。

在这里插入图片描述

  • 优化:尽量减小写传播给总线带来的带宽压力
    • 两个策略:
      • 写传播:如何减少无效的监听,减小总线带宽的压力。
      • 串行化机制:如何锁总线。
  • MESI 一致性协议
    • 基于总线嗅探机制实现了事务串行化,通过状态机降低总线带宽的压力
    • 4 个状态
      • Modified:已修改,某个数据块已修改但是没有同步到内存中。
      • Exclusive:独占,某个数据块只在某核心的缓存中,并且此时缓存和内存中的数据是一致的。
      • Shared:共享,某个数据块在多个核心的缓存中,并且此时缓存和内存中的数据是一致的。
      • Invalidated:已失效,某个数据块在核心中已失效,不是最新的数据。
        • core0 中的数据:i = 5,core1 中的数据:i = 5,内存中的数据:i = 5;当 core0 写 i = 10 时,会通过总线嗅探,将 core1 中的数据 i = 5 的状态修改为 Invalidated。
    • 锁住 M 和 E 状态(因为 M 和 E 状态是不需要广播的),避免相关内存的访问

三、内存序

  • 原子性还没有解决避免竞态条件的问题
  • 为什么会有内存序问题
    • 编译器优化重排
      • C++ 在编译代码的时候,为了提高未来运行的效率,编译器会对代码指令进行编译优化重排。
    • CPU 指令优化重排
      • CPU 在运行的时候,也会对指令进行优化重排:在实现原子性的时候,会锁 M、E 状态,既然核心不能操作相关的内存区域,那就去操作不相关的内存区域,这样核心就不会干等着,从而提高整体的运行效率。
    • 在 CPU 看来,i 和 j 没有任何关系,可以并行处理。但程序的逻辑是:在 j += 2 的时候,i 已经 += 1了,在多线程条件下,就会出现竞态问题。
      int i = 0;
      int j = 0;
      i += 1;
      j += 2;
      
  • 内存序规定了什么
    • 规定了多个线程访问同一个内存地址时的语义
      • 同步性某个线程对内存地址的更新何时能被其它线程看见
      • 顺序性某个线程对内存地址访问附近可以做怎么样的优化
  • 内存模型
    • 这里所指的内存模型对应缓存一致性模型,作用是对同一时间的读写操作进行排序,在不同的 CPU 架构上,这些模型的具体实现方式可能不同,但是 C++ 11 屏蔽了内部细节,不用考虑内存屏障。可能有时使用的模型粒度比较大,会损耗性能,当然还是使用各平台底层的内存屏障粒度更准确,效率也会更高。
    • memory_order_relaxed:松散内存序。
      • 只用来保证对原子对象的操作是原子的,在不需要保证顺序时使用。
      • 读操作和写操作都可以使用。
      • 效率最高。
      // 没有同步性,其它线程可能读到的不是最新的值
      // 不干预 编译器或 CPU 的优化
      s.load(std::memory_order_relaxed);
      
      在这里插入图片描述
    • memory_order_release:释放操作。
      • 在写入某原子对象时,当前线程的任何前面的读写操作都不允许重排到这个操作的后面去并且保证其它线程可以读取到该原子对象的最新值
      • 通常与 memory_order_acquire 配对使用。
      • 只能在写操作中使用。
      • 依据前面的才写入
      // 具备同步性,其它线程读到的是最新的值
      // 干预了优化
      s.store(10, std::memory_order_release);
      
      在这里插入图片描述
    • memory_order_acquire:获取操作。
      • 在读取某原子对象时,当前线程的任何后面的读写操作都不允许重排到这个操作的前面去并且保证当前线程可以读取到该原子对象的最新值
      • 只能在读操作中使用。
      • 后面的依据读取的
      // 具备同步性,当前线程读到的是最新的值
      // 干预了优化
      s.load(std::memory_order_acquire);
      
      在这里插入图片描述
    • memory_order_acq_rel:获得释放操作。
      • 一个读 — 修改 — 写操作,同时具有获得语义和释放语义,即它前后的任何读写操作都不允许重排,并且保证其它线程可以读取到该原子对象的最新值、当前线程可以读取到该原子对象的最新值。
    • memory_order_seq_cst:顺序一致性语义。
      • 对于读操作相当于获得,对于写操作相当于释放,对于读 — 修改 — 写操作相当于获得释放。
      • 是所有原子操作的默认内存序,并且会对所有使用此模型的原子操作建立一个全局顺序,保证了多个原子变量的操作在所有线程里观察到的操作顺序相同。
      • 效率最低。
#include <atomic>
#include <thread>
#include <assert.h>
#include <iostream>

// g++ relaxed.cc -o relaxed -lpthread

std::atomic<bool> x, y;
std::atomic<int> z;

void write_x_then_y()
{
    x.store(true,std::memory_order_relaxed);  // 1
    y.store(true,std::memory_order_relaxed);  // 2
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_relaxed));  // 3
    if(x.load(std::memory_order_relaxed))  // 4
        ++z;
}
// z 会不会等于 1 ?  不一定

int main()
{
    for (int i = 0; i < 100000; i++) {
        x = false;
        y = false;
        z = 0;
        std::thread b(read_y_then_x);
        std::thread a(write_x_then_y);
        b.join();
        a.join();
        int v = z.load(std::memory_order_relaxed);
        if (v != 1)
            std::cout << v << std::endl;
    }
    return 0;
}
#include <atomic>
#include <thread>
#include <assert.h>
#include <iostream>

// g++ acquire_release.cc -o acquire_release -lpthread

std::atomic<bool> x,y;
std::atomic<int> z;

void write_x_then_y()
{
    x.store(true,std::memory_order_relaxed);  // 1 
    y.store(true,std::memory_order_release);  // 2   y = true x= true
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_acquire));  // 3 自旋,等待 y 被设置为true
    if(x.load(std::memory_order_relaxed))  // 4
        ++z;
}
// z 能确保读到 1

int main()
{
    x = false;
    y = false;
    z = 0;
    std::thread a(write_x_then_y);
    std::thread b(read_y_then_x);
    a.join();
    b.join();
    std::cout << z.load(std::memory_order_relaxed) << std::endl;
    return 0;
}

四、互斥锁

  • 如何实现互斥锁:
    • 互斥锁首先需要做一个内存标记记录的是线程 ID,因为互斥锁需要进行线程切换。
    • 互斥锁是为了保护临界资源,同时只允许一个线程去访问临界资源,所以会有一个阻塞队列,通过阻塞队列唤醒其它线程去访问临界资源。
    • 要屏蔽中断。
    • 底层硬件自旋锁。
  • 互斥锁的表现:
    • 先在用户态自旋一会儿。
    • 获取失败,把任务挂起(放到阻塞队列中),核心会切换其它线程去执行。
    • 休眠一段时间再次尝试获取锁。

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

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

相关文章

第十讲 Query Execution Part 1

1 处理模型【Processing Model】 DBMS 的处理模型【Processing Model】定义了系统如何执行【execute】查询计划【Query Plan】。 针对不同的工作负载进行不同的权衡。 方法1&#xff1a;迭代器模型【Iterator Model】 方法2&#xff1a;物化模型【Materialization Model】 方…

linux虚拟机上安装,使用以及远程连接mysql

1. 安装mysql 5.7 1) 首先更新软件源 sudo apt-get update 2) 安装MySQL数据库软件 ​ sudo apt-get install mysql-server 3) 安装MySQL数据库管理软件​ sudo apt-get install mysql-client 4) 安装MySQL数据库客户端&#xff0c;用户访问数据库 sudo apt-get install…

JAVA—抽象—定义抽象类Converter及其子类WeightConverter

同样&#xff0c;我们由这道题引出抽象类&#xff0c;抽象方法这个概念。 按下面要求定义类Converter及其子类WeightConverter 定义抽象类&#xff1a;Converter&#xff1a; 定义一个抽象类Converter&#xff0c;表示换算器&#xff0c;其定义的如下&#xff1a; 一个私有…

HarmonyOS实战开发DLP-如何实现一个安全类App。

介绍 本示例是一个安全类App&#xff0c;使用ohos.dlpPermission 接口展示了在eTS中普通文件加密受限的过程。 效果预览 使用说明: 1.启动应用后点击“”按钮可以添加一个普通文件; 2.长按点击加密按钮&#xff0c;出现加密权限弹窗&#xff0c;选择需要设置的权限并点击确定…

【C语言】_文件类型,结束判定与文件缓冲区

目录 1. 文本文件和二进制文件 2. 文件读取结束的判定 3. 文件缓冲区 1. 文本文件和二进制文件 根据数据的组织形式&#xff0c;数据文件被称为文本文件或二进制文件&#xff1b; 数据在内存中以二进制的形式存储&#xff0c;如果不加转换地输出到外存&#xff0c;就是二进…

蓝桥杯刷题-13-子矩阵-二维滑动窗口 ಥ_ಥ

给定一个 n m &#xff08;n 行 m 列&#xff09;的矩阵。 设一个矩阵的价值为其所有数中的最大值和最小值的乘积。求给定矩阵的所有大小为 a b &#xff08;a 行 b 列&#xff09;的子矩阵的价值的和。 答案可能很大&#xff0c;你只需要输出答案对 998244353 取模后的结果。…

Redis 的主从复制、哨兵和cluster集群

目录 一. Redis 主从复制 1. 介绍 2. 作用 3. 流程 4. 搭建 Redis 主从复制 安装redis 修改 master 的Redis配置文件 修改 slave 的Redis配置文件 验证主从效果 二. Redis 哨兵模式 1. 介绍 2. 原理 3. 哨兵模式的作用 4. 工作流程 4.1 故障转移机制 4.2 主节…

Java开发测试(第一篇):Java测试框架JUnit5

目录 1.基本介绍 2.maven中安装JUnit5 3.使用 4.JUnit5命名规则 5.JUnit5常用注解 6.JUnit5断言 7.JUnit5多个类之间的继承关系 8.JUnit5参数化 &#xff08;1&#xff09;使用场景&#xff1a; &#xff08;2&#xff09;使用前需在pom.xml文件中导入依赖 &#xff…

海纳斯删除广告位

找到文件 vim /var/www/html/home.php 删除代码段 <div class"adleft" id"adleftContainer"><button onclick"closeAd()">关闭</button><a href"https://www.ecoo.top/ad.html" target"_blank">&l…

怀俄明探空站数据解算PWV和Tm

1. Matlab 获取代码可关注公众号WZZHHH回复&#xff08;怀俄明探空站数据解算PWV和Tm&#xff09;&#xff0c;或者咸鱼关注&#xff1a;WZZHHH123 怀俄明探空站数据解算PWV和Tm&#xff1a; 有关 Matlab 获取代码可关注公众号WZZHHH回复&#xff08;怀俄明多线程下载&#…

最新怎么订阅OnlyFans上喜欢的博主,详细教程

大家好&#xff0c;本文教大家如何用虚拟信用卡在 Onlyfans 订阅&#xff0c;链接在浏览器打开地址https://bewildcard.com/i/GPT310&#xff0c;虚拟卡开好之后&#xff0c;用支付宝充值就可以进行订阅OnlyFans平台的博主了。 什么是OnlyFans&#xff1f; OnlyFans 是一个提…

毅速解析:金属3D打印模具工件性能如何?

3D打印技术&#xff0c;在模具随形水路、异形模具制造以及模具排气结构等方面的独特优势&#xff0c;已成为模具升级的重要技术方向&#xff0c;并受到众多注塑、压铸等模具行业的青睐。 然而&#xff0c;面对这一新兴技术&#xff0c;不少人不免心生疑虑&#xff1a;3D打印技术…

【c++练习】求3个长方柱的体积

【问题描述】编写一个基于对象数组的程序&#xff0c;用成员函数实现多个功能&#xff0c;求3个长方柱的体积。要求用成员函数实现以下功能&#xff1a; 1、由键盘分别输入3个长方柱的长、宽、高&#xff1b; 2、计算长方柱的体积&#xff1b; 3、输出3个长方柱的体积。 【…

MySQL基础【语句执行顺序】

一个SQL语句它的执行顺序对于我们思考题意有着很重要的关系 题意就是&#xff1a;找出哪些只逛超市不买单的人&#xff08;买单0元也算哦&#xff0c;可能是使用的是代金券吧&#xff09; 看到此题关键找出两个数据 参观过的人 和 买单的人 他们的差就是白嫖的人&#xff08;支…

C语言分支语句

一、什么是语句 C语句可分为以下五类&#xff1a; 表达式语句 函数调用语句 控制语句 复合语句 空语句 本周后面介绍的是控制语句。 控制语句用于控制程序的执行流程&#xff0c;以实现程序的各种结构方式&#xff0c;它们由特定的语句定义符组成&#xff0c;C语 言有…

远程过程调用(远程调用)

远程过程调用&#xff08;远程调用&#xff09; 1、什么是分布式计算 在计算机科学中&#xff0c;分布式计算&#xff08;英语&#xff1a;Distributed computing&#xff09;&#xff0c;又译为分散式运算。这个研究领域&#xff0c;主要研究分布式系统&#xff08;Distribu…

什么是MQ ?为什么用MQ?

什么是MQ&#xff1f; MQ(message queue)&#xff08;消息队列&#xff09;&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO先入先出&#xff0c;只不过队列中存放的内容是message而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消息…

春招-实战项目冲刺直播课

春招-实战项目冲刺直播课 CCtalk 丰富多元的综合内容平台-专业的知识分享与在线教育平台https://www.cctalk.com/m/group/91161801

解决win7作为虚拟机无法复制粘贴共享文件的问题

win7作为虚拟机经常会出现无法与主机的剪切板共享、文件共享。 归根结底是win7虚拟机里面没有安装VMware Tools 能够成功安装vmware tools的条件&#xff1a; 1&#xff09;win7版本为win7 sp1及以上 2&#xff09;安装KB4490628&#xff0c;KB4474419补丁 因此下面来详细介绍…

QA测试开发工程师面试题满分问答9: Python中内存管理的概念、原理、使用

概念原理 Python中的内存管理是由解释器自动处理的&#xff0c;它使用引用计数和垃圾回收机制来管理内存。以下是Python内存管理的一些关键概念、设计原理和最佳实践&#xff0c;以帮助您高效使用和管理内存&#xff1a; 引用计数&#xff1a;Python使用引用计数来追踪对象的引…