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

一、原子变量、原子操作

  • 锁竞争:互斥锁条件变量、原子变量、信号量、读写锁、自旋锁。
  • 在高性能基础组件优化的时候,为了进一步提高并发性能,可以使用原子变量。
  • 性能:原子变量 > 自旋锁 > 互斥锁
    • 操作临界资源的时间较长时使用互斥锁(粒度大)
    • 如果只操作单个变量的话,可以使用原子变量(粒度小) 进行优化。
  • 给基础类型或者指针加上一个标记 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,因为互斥锁需要进行线程切换。
    • 互斥锁是为了保护临界资源,同时只允许一个线程去访问临界资源,所以会有一个阻塞队列,通过阻塞队列唤醒其它线程去访问临界资源。
    • 要屏蔽中断。
    • 底层硬件自旋锁。
  • 互斥锁的表现:
    • 先在用户态自旋一会儿。
    • 获取失败,把任务挂起(放到阻塞队列中),核心会切换其它线程去执行。
    • 休眠一段时间再次尝试获取锁。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【leetcode】动态规划::前缀和

标题&#xff1a;【leetcode】前缀和 水墨不写bug 正文开始&#xff1a; &#xff08;一&#xff09;简单前缀和 描述 给定一个长度为n的数组a1​,a2​,....an​. 接下来有q次查询, 每次查询有两个参数l, r. 对于每个询问, 请输出al​al1​....ar​ 输入描述&#xff1a; 第一…

删除mysql表卡死 , 打不开,一直转圈圈

最近用navicat删除某一张表时&#xff0c;直接卡死转圈圈&#xff0c;导致navicat直接无响应, 想着是不是自己navicat有问题&#xff0c;换同事电脑来删这张表&#xff0c;还是同样问题。 多次尝试才整明白&#xff0c;根本不是navicat的问题.是mysql 的表锁死了! 如果频繁的对…

如何明确的选择IT方向?

一、明确目标 作为初学者&#xff0c;先树立自己目标&#xff0c;找到自己感兴趣的IT行业&#xff0c;IT行业分很多种&#xff0c;听的最多次的无非不就是web前端工、程序员、后端、大数据、网络运维等。学习知识也是为了找到更好的工作&#xff0c;所以我建议先去boss直聘、五…

MyBatis 入门使用(二)

MyBatis的开发有两种方式&#xff1a;注解和XML&#xff0c;上一期我们学习了使用注解的方式&#xff0c;这期我们学习XML的方式。 使用注解主要是用来完成一些简单的增删改查功能&#xff0c;如果需要实现复杂的SQL功能&#xff0c;建议使用XML来配置映射语句。 1. 使用步骤…

day02 VS Code开发单片机

VS Code开发单片机 1.1 安装 MinGW-w64 1)MinGW-w64介绍 VS Code 用于编辑 C 代码,我们还需要 C 编译器来运行 C 代码,所以安装 VS Code之前我们需要先安装 C 编译器。这里我们使用 MinGW-w64(Minimalist GNU for Windows 64-bit)。 MinGW-w64 是一个用于Windows操作系…

Transformer模型-broadcast广播的简明介绍

broadcast的定义和目的&#xff1a; 广播发生在将较小的张量“拉伸”以具有与较大张量兼容的形状&#xff0c;以便执行操作时。 广播是一种有效执行张量操作而不创建重复数据的方式。 广播的处理过程&#xff1a; 1&#xff0c; 确定最右边的维度是否兼容 每…

2024/4/7 IOday6

1&#xff1a;有一个隧道&#xff0c;全长5公里&#xff0c;有2列火车&#xff0c;全长200米&#xff0c; 火车A时速 100公里每小时 火车B时速 50公里每小时 现在要求模拟火车反复通过隧道的场景(不可能2列火车都在隧道内运行) #include <stdio.h> #include <string.…

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

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

tabby 创建ssh远程配置提示:Timed out while waiting for handshake

不知道是不是网络延迟还是虚拟机克隆链接的问题&#xff0c;使用tabby无法正常的ssh远程过去&#xff0c;链接提示信息如下&#xff1a; SSH Connecting to 192.168.36.10SSH ! Agent auth selected, but no running agent is detectedSSH Host key fingerprint:SSH ecd…

Android匿名共享内存(Ashmem)

在Android中我们熟知的IPC方式有Socket、文件、ContentProvider、Binder、共享内存。其中共享内存的效率最高&#xff0c;可以做到0拷贝&#xff0c;在跨进程进行大数据传输&#xff0c;日志收集等场景下非常有用。共享内存是Linux自带的一种IPC机制&#xff0c;Android直接使用…

深入解析War包和Jar包机制

一、概述 代码编写完成后&#xff0c;需要部署到服务器&#xff0c;但部署到服务器对文件格式是有要求&#xff0c;原生的源代码目前是无法支持直接部署到服务器上的。目前有两种主要的文件格式War包和Jar包&#xff0c;通过一定的机制将源代码变成War包或Jar包&#xff0c;就…

42. 接雨水(Java)

目录 题目描述:输入&#xff1a;输出&#xff1a;代码实现&#xff1a; 题目描述: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 输入&#xff1a; height [0,1,0,2,1,0,1,3,2,1,2,1]输出&#xff1…

WebKit是什么?

WebKit是一个开源的浏览器引擎&#xff0c;它用于呈现网页内容在许多现代浏览器中&#xff0c;包括Safari浏览器、iOS内置浏览器、以及一些其他浏览器如Google Chrome的早期版本。以下是一些关于WebKit的重要信息&#xff1a; 起源和发展&#xff1a;WebKit最初是由苹果公司为其…

上传文件报错e20004 阿里云盘:空间不足 送的空间突然全到期了。免费无法长久 百度网盘扛住了压力,没有跟风。

https://blog.csdn.net/chenhao0568/article/details/137332783?spm1001.2014.3001.5501 免费撑不住了&#xff0c;这样下去干不过老大呀。百度网盘扛住了压力&#xff0c;没有跟风。

计算机网络——34LANs

LANs MAC地址和ARP 32bit IP地址 网络层地址用于使数据到达目标IP子网&#xff1a;前n - 1跳从而到达子网中的目标节点&#xff1a;最后一跳 LAN&#xff08;MAC/物理/以太网&#xff09;地址&#xff1a; 用于使帧从一个网卡传递到与其物理连接的另一个网卡&#xff08;在同…

计算机网络练习-计算机网络概述与性能指标

计算机网络概述 ----------------------------------------------------------------------------------------------------------------------------- 1. 计算机网络最据本的功能的是( )。 1,差错控制 Ⅱ.路由选择 Ⅲ,分布式处理 IV.传输控制 …

人眼对亮度的感知

对比两本书的说法 计算机图形学的算法基础 david f.rogers 如图所示: 然后看数字图像处理_第三版_中_冈萨雷斯的说法&#xff1a; 视觉错觉对于做图像处理没有什么大用。前面两点有用。 第一点。马赫带效应&#xff0c;明暗变化太强的时候&#xff0c;出现马赫带。较明区域是…

蓝桥杯刷题-14-更小的数-区间DP⭐

蓝桥杯2023年第十四届省赛真题-更小的数 //区间DP #include <iostream> #include<bits/stdc.h> #define int long long using namespace std; const int N5e310; int f[N][N]; void solve(){string s;cin>>s;int ans0;for(int len2;len<s.size();len){for…

Sora是什么?Sora怎么使用?Sora最新案例视频以及常见问题答疑

Sora 是什么&#xff1f; 2024年2月16日&#xff0c;OpenAI 在其官网上面正式宣布推出文本生成视频的大模型Sora 这样说吧给你一段话&#xff0c; 让你写一篇800字的论文&#xff0c;你的理解很可能都有偏差&#xff0c;那么作为OpenAi要做文生视频到底有多难&#xff0c;下面…

牛市来临,模块化赛道可能会出现下个以太坊?

市场专家普遍预测&#xff0c;2024年将成为加密货币市场迎来新一轮牛市的关键时刻。研究人员将下一次比特币&#xff08;BTC&#xff09;减半以及2024年现货BTC ETF&#xff08;交易所交易基金&#xff09;的可能性视为推动下一次牛市的潜在因素。这一牛市的可能爆发有望吸引大…