彻底搞懂内存屏障(上)

Store buffer 解决CPU的停滞(stall)状态 此外,由于store buffer的存在,写操作的顺序也可能被打乱。例如,CPU1可能先执行了一个写操作A,然后执行了一个写操作B,但是由于写操作B的数据先被放入store buffer并随后被刷新到主存,而写操作A的数据还在store buffer中等待,所以其他处理器看到的写操作顺序可能是B-A,而不是A-B。在多线程环境下,当一个线程将一个变量的值写入Store buffer后,其他线程可能立即读取这个变量的值,但由于Store buffer的异步性,这个读取操作可能会得到旧的值,而不是新写入的值。 阅读详情
AI摘要

本文通过一个典型的并发编程示例,深入探讨了多处理器系统中的内存屏障问题。解释了现代CPU架构中store buffer和invalidate queue如何引发内存乱序,并提出了相应的解决办法。

1.从一个示例代码说起

探讨内存屏障的问题基本都会从如下代码作为示例讲解: 

//假设a和b初始化为0 ,CPU 0执行foo函数,CPU 1执行bar函数。我们再进一步假设a变量
//在CPU 1的cache中,b在CPU 0 cache中,执行的操作序列如下:
CPU0:
void foo(void) {
    a = 1;
    b = 1;
}

CPU1:

void bar(void) {
    while (b != 1); 
    assert (a == 1);
}

两方便原因可能导致assert失败:

  •  编译层面:
    • 如果前后代码无数据依赖关系,如果编译优化选项(-O2或者-O3)级别比较高会发生代码乱序,提升性能。
    • 注意:c/c++的关键字volatile只保证内存即使从寄存器回写到主存,没有内存屏障功能,也不保证原子性。
  • CPU层面:
    • 现在的CPU处理器支持乱序执行(out-of-order)。如果刚刚接触这个概念,十有八九会对乱序执行产生误解,以为是因为单纯的cpu指令乱序执行导致了上面示例代码的assert失败,其实不然。

乱序执行的本质: 

乱序执行是说,给定一串执行指令,cpu为了提升执行效率,会找出没有数据依赖的指令,让他们并行执行,但是,执行执行结果写回寄存器是顺序的。哪怕是先被执行的指令,它的运算结果也是按照指令次序写回到最终的寄存器的。这个和很多程序员理解的乱序执行是有区别的。所以在单核处理器系统中,虽然CPU内部支持乱序执行,但是CPU会保证最终执行结果符合程序员的要求,这种情况下不需要使用内存屏障。这里从linux内核内存屏障函数定义也可以看出:

#if defined(CONFIG_ARM_DMA_MEM_BUFFERABLE) || defined(CONFIG_SMP)
#define mb()        __arm_heavy_mb()
#define rmb()       dsb()
#define wmb()       __arm_heavy_mb(st)
#define dma_rmb()   dmb(osh)
#define dma_wmb()   dmb(oshst)
#else
#define mb()        barrier()                                                                                                                              
#define rmb()       barrier()
#define wmb()       barrier()
#define dma_rmb()   barrier()
#define dma_wmb()   barrier()
#endif

可以看到如果是非SMP系统,内存屏障都定义成barrier()函数,这个函数就是编译器层面的内存屏障函数,避免编译器乱序执行代码。

2.为什么SMP系统会乱序

乱序的核心原因是现代cpu为了追求性能,实现的memory model有关,现代cpu内部有store buffer/cache/invaliate queue和缓存一致性协议的方案导致乱序的可能。尤其arm64处理起是强乱序的处理起,读写,写读,读读,写写都可能乱序。

2.1 现代cpu架构存储图:

store buffer:

举例说明引入store buffer原因:

  • 变量不在cpu的cache中,cpu想修改这个变量,需要发送read invalidate信号,等待信号返回之后才可以写入缓存cache中,如果cpu核数很大,等到每个cpu响应该信号导致性能差。
  • 变量量在该CPU缓存中,如果该量的状态是exclusive则直接更改。而如果是shared则需要发 送invalidate消息让其它CPU感知到这一更改后再更改。

有了store buffer之后,cpu可以将变量写入store buffer,然后去忙其他事情,其他cpu响应信号之后再将store buffer中的变量写入缓存。

2.2 store buffer引入的乱序

store buffer提升了性能(本处解释假设不存在invalidate queue,方便理解问题),却引入了复杂性,考虑如下顺序:

  1. cpu0执行a = 1指令,其cache中值是0,那么由于store buffer的引入,cpu0直接将a = 1写入了store buffer,不需要等待cpu1的信号响应就可以执行b = 1。
  2. cpu0执行b = 1时,由于b在cpu0中的状态是exclusive独占(可以去参考文章看MESI协议),cpu0直接将b = 1写入了cpu 0 cache中,那么cpu1执行b != 1 成功,然后执行assert( a == 1),由于cpu0需要等到cpu1响应完invalidate才将 a= 1写入cache中(目前还在store buffer中),所以cpu1读取到的任然是0,assert失败。

 从图例可以看到第六步assert失败的核心原因在于,cpu0上缓存过b,所以cpu0执行b = 1k立马写入cache,然后线程B所在的cpu1执行while(b==0)迅速跳出,然后执行 a = 1,由于此时 a 还在cpu0的store buffer中,所以导致assert失败。

2.3 解决方案

void foo(void) {
    a = 1;
    smp_wmb();
    b = 1;
}

smp_mb保证 b = 1执行前,先把store buffer内存刷新到cache中。CPU1 执行assert( a == 1),发现a 不在cache中,向CPU0发送read消息,CPU0的cache中存在a,所以CPU1获取到a = 1,assert成功。

2.4 invalidate queue引入的乱序

引入原因:

由于store buffer的大小是有限,上面例子中如果cpu1无法快速处理read invalidate消息,那么cpu0中的store buffer马上就会满了,所以arm增加了invalidate queue存放收到invalidate消息,收到invalidate消息放入invalidate queue中,然后给对方(cpu0)发送一个respone响应,但是此时并不真正处理invalidate消息,正因如此,invalidate queue又引入新的复杂性。

invalidate queue导致的乱序:

还是使用上面的代码,始状态CPU0拥有b=0(独有),存有a=0(shared),CPU1存有a=0(shared),CPU0执行foo,CPU1执行bar。

1和2.  CPU0 执行 a = 1,由于a是shared的状态,不能直接写入cache,先放入store buffer,发送invalidate消息(等到CPU1响应Invalidate消息才从store buffer写入cache)

3. CPU1执行while 循环,由于cache中不存在b,发送read消息给CPU0。

4.CPU0执行b = 1,由于b是cache独有,直接写入cache

5. CPU1收到Invalidate消息,直接放入invalidate queue

6. CPU1收到read消息回复,同步到b = 1,结束while循环

7. CPU1执行assert,由于此时CPU1未处理invalidate消息,cache中 a = 0,所以assert失败。

这种情况失败的主要原因是,CPU1不及时的处理invalidate queue的消息,导致cache中的数据是失效的。

2.5 解决方案

void bar(void) {
    while (b != 1); 
    smb_rmb();
    assert (a == 1);
}

3. 内存屏障指令

参考:ARM64中的内存屏障指令 - 知乎

参考文章:

Why Memory Barriers?中文翻译(上)3

ARM64中的内存屏障指令 - 知乎

https://people.freebsd.org/~lstewart/articles/cpumemory.pdf

CPU乱序执行_楓潇潇的博客-优快云博客_cpu乱序执行

什么是内存屏障?为什么需要内存屏障? 1.什么是内存屏障?,为什么需要内存屏障? 首先,为了性能编译器和处理器都会对指令进行重排序。 什么是内存屏障:内存屏障是一条指令,该指令可以对编译器(软件)和处理器(硬件)的指令重排做出一定的限制,比如,一条内存屏障指令可以禁止编译器和处理器将其后面的指令移到内存屏障指令之前。 为什么需要内存屏障:编译器和处理器指令重排只能保证在单线程执行下逻辑正确,在多个线程同时读写多个变量的情况下,如果不对指令重排作出一定限制,代码的执行结果会根据指令重排后的顺序产生不同的结果。指令重排后的顺序每次执行时都可能 阅读详情

相关推荐

内存屏障详解

这样,sfence之前的所有store(包括写v)不会被重排序到sfence之后,sfence之后的所有store不会被重排序到sfence之前,禁用跨sfence的store重排序;然而,这种重排序的危害要远远大于单纯的可见性,因为并不是所有的指令都是简单的读或者写——面试中单例模式有几种写法?强制所有在lfence指令之后的load指令,都在该lfence指令执行之后被执行,并且一直等到load buffer被该CPU读完才能执行之后的load指令(发现缓存失效后发起的刷入)。

m0_74282605的博客 1785

关于缓存一致性协议、MESI、StoreBuffer、InvalidateQueue、内存屏障、Lock指令和JMM的那点事

事情是这样的,一位读者看了我的一篇文章,不认同我文章里面的观点,于是有了下面的交流。可能是我发的那个狗头的表情,让这位读者认为我不尊重他。于是,这位读者一气之下把我删掉了,在删好友之前,还叫我回家种田.........

CoderW 5181

卷积神经网络-猫狗识别(附源码)

利用python实现一个卷积神经网络,训练猫狗数据集,实现对猫狗图片的预测识别

m0_63223213的博客 2万+

内存屏障今生之Store Buffer, Invalid Queue

在「前置内容」中,我们了解到「最原始」的cpu是如何在缓存一致性协议MESI的指导下工作的,同时我们也发现此时cpu的性能因为同步请求远程(其他cpu)数据而大打折扣。本文章将介绍cpu的优化过程,你将了解到硬件层面的优化——Store Buffer, Invalid Queue,以及软件层面的优化——cpu内存屏障指令。最原始的架构性能优化之旅——Store Buffer当cpu需要的数据在其他cpu的cache内时,需要请求,并且等待响应,这显然是一个同步行为,优化的方案也很明显,采用异步。思路大概是在

wll1228的博客 1万+

阿里巴巴-AI Agent工程师面试题精选:10道高频考题+答案解析(附PDF)

阿里巴巴AIAgent技术面试要点摘要(149字) 阿里巴巴在AIAgent领域布局完整,面试重点考察系统架构、幻觉治理、成本优化等核心能力。候选人需掌握分层架构设计、多Agent协作死锁处理、Token成本优化策略等关键技术。面试强调工程落地能力,包括工具调用容错、记忆系统设计、安全防护等实践要点。评估体系需覆盖效果、效率、成本多维度指标。未来趋势关注垂直领域专业化、多模态融合等方向。建议准备实际项目案例,突出技术深度与业务价值的结合,展现系统思维和工程能力。

ZHHHHH15的博客 614

RL调参侠之BipedalWalker BipedalWalkerHardcore SAC

前几天用PPO训练了个很一般的agent: RL调参侠之BipedalWalker PPO 我看SpinningUp上面说PPO和SAC是两个领域里各自的State_of_the_art的方法,所以就打算跑SAC试试看,之前因为Open AI Five的原因,一直感觉PPO很牛掰,实际上现在很多自动驾驶的算法也是用PPO在跑,毕竟有一个“必定能够提升期望”的理论保证在这里,我理解这对做项目很大的诱惑。 不过我跑出来的结果明显是SAC更好。 调参侠时间 策略熵的权重alpha用的0.2,我一开始没有看SAC的

weixin_48370148的博客 2099

彻底搞懂内存屏障(下)

线程cosume中使用 memory_order_acquire内存屏障,保证了consumer线程后面的所有读写(包括非原子)操作不能重排序到acquire之前,并且consumer的load操作一旦完成,线程producer release之前的读写操作对producer线程可见。我们知道,acquire/release只保证load完成之后,release之前的内存操作在load之后是可见,但是并不保证一个线程中的store(release)操作,立马对其他线程的load(acquire)可见。

GetnextWindow的专栏 4938

从零开始构建高精度称重系统:ADS1232硬件设计与软件调优实战

本文详细介绍了如何基于TI的ADS1232 24位ADC芯片构建高精度称重系统,涵盖硬件设计、软件调优及校准算法。通过优化电源设计、PCB布局和卡尔曼滤波算法,实现毫克级测量精度,适用于工业自动化和实验室等高要求场景。

weixin_28285943的博客 382

彻底搞懂 Linux 内核的内存屏障机制

一句话记住:架构的内存模型越弱,对内存屏障的依赖越强;反之,强内存模型可以大幅减少屏障的使用,降低性能开销。往期精选干货| C/C++开发进阶攻略👉【大厂标准】Linux C/C++ 后端进阶学习路线👉 LinuxLinux 内核学习指南,硬核修炼手册👉C/C++ 高频八股文面试题 1000 题(三)

hw5230的博客 726

雷赛控制卡全方位使用指南:从硬件连接到高级应用

雷赛控制卡以其接口简单、编程便捷、性价比高的特点,成为中小自动化设备的理想选择。使用时需注意:硬件连接需注重抗干扰设计(屏蔽线、接地);软件开发需合理配置运动参数(脉冲当量、加减速);调试阶段需充分利用 DMCStudio 工具验证性能。通过本文介绍的方法,可快速实现从硬件搭建到复杂运动控制的全流程开发,满足雕刻、切割、装配等多场景的自动化需求。

品质生活,极致编程。 3770

利用makehumancommunity导入人体模型到COMSOL里(需要solidwork)

#makehumancommunity #solidwork #COMSOL

qq_41227634的博客 2158

【C语言】彻底搞懂内存屏障与volatile

点击蓝字关注我们因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络,侵删最有价值的写在最前面内存屏障与 volatile 是高并发编程中比较常用的两个技术,无锁队列的时候就会用到这两项技术。然而这两项技术涉及比较广的基础知识,所以比较难以理解,也比较不容易解释清楚。关于内存屏障和 volatile 网上有相当多的资料,但是总感觉还是不够系统和深入。当然由于我自身水平有...

轻松学C语言 1102

基于STM32F4XX 、HAL库的SHT40-CD1B-R3温湿度传感器驱动应用C语言程序设计

define SHT40_ADDR_READ 0x8B // CD1B 读地址 ((0x45 << 1) + 1)#define SHT40_ADDR_WRITE 0x8A // CD1B 写地址 (0x45 << 1)// 初始化 I2C1 (根据 CubeMX 生成的函数)// 湿度: -6%RH + 125%RH * (raw / 2^16)// 温度: -45°C + 175°C * (raw / 2^16)// 返回值: 0=成功, 1=失败(CRC错误或I2C通信错误)

PP_Cottage的博客 236

volatile难道只是可见性?从JMM到内存屏障彻底搞懂Java并发

多线程并发编程中,共享变量的可见性问题常常导致程序出现难以复现的“假死”或数据错乱。Java内存模型(JMM)通过主内存与工作内存的抽象,解释了为什么一个线程修改变量后,另一线程可能读不到最新值。volatile正是针对这一场景的轻量级同步机制。它利用缓存一致性协议与内存屏障,保证写操作能刷新到主内存、读操作能重新加载,从而解决可见性;同时通过禁止指令重排序维持有序性。值得注意的是,volatile并不能保证复合操作的原子性,自增场景仍会丢更新。实际应用中,状态标志、不可变对象的安全发布、DCL单例等都依赖

weixin_32466193的博客 263

016、自动标注方案实战:用大模型(SAM/Grounding DINO)生成 YOLO 格式伪标签

本文介绍了一种利用大模型自动生成YOLO格式伪标签的实战方案。针对传统人工标注效率低、易出错的问题,作者提出结合Grounding DINO(零样本检测)和SAM(精细分割)的优势:前者快速生成候选框,后者优化边界精度。文章详细阐述了技术选型理由(平衡速度与精度)、环境搭建避坑指南(关键依赖版本)、核心三步流程(检测-分割-格式转换)以及后处理技巧(置信度分级过滤)。实验表明,该方案相比纯人工标注可节省80%时间,且经过SAM优化的边界框精度提升10-15%。特别强调了工程细节:文本描述格式、阈值设定、小目

zzfmlxs的博客 543

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

彻底搞懂C++内存屏障:从基础到实战的memory_order完全指南

在多线程编程中,内存屏障(Memory Barrier)是确保共享数据访问正确性的关键机制。本文将以gh_mirrors/st/STL项目为基础,全面解析C++内存模型中的memory_order枚举类型及其在实际开发中的应用场景,帮助开发者编写高效且安全的并发代码。 ## 一、内存屏障与memory_order的核心作用 内存屏障是一种同步原语,用于控制CPU对内存操作的重排序,确保多线程环

gitblog_00594的博客 936

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

《彻底搞懂volatile关键字:可见性、有序性、内存屏障全解析》

volatile是Java中的一个关键字,用来修饰变量。它的主要作用是告诉JVM:“这个变量可能会被多个线程同时访问和修改,你要特别注意它的读写操作!” 🚨举个生活中的例子🌰:想象你在教室里和同学们一起玩传话游戏。老师把一句话告诉第一个同学,然后依次传下去。如果中间有同学记错了,最后的结果就会出错。volatile就像是老师用大喇叭📢对全班同学同时宣布消息,确保所有人都能直接听到原始信息,而不是通过中间人传递。今天我们深入探讨了Java中的volatile是什么。

龚小帅的博客 2202

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

(转载)一文彻底搞懂并发编程与内存屏障(IT知识刺客)

详细而生动的解释了内存屏障的概念,并且结合多核架构进行分析,最后结合实例讲解,深入浅出,收益颇多。

梅花香自苦寒来,宝剑锋从磨砺出。 669

彻底搞懂Triton内存屏障:多线程同步与一致性保证实战指南

你是否在编写Triton内核时遇到过共享内存访问冲突?是否因缺乏同步机制导致结果不一致?本文将从实战角度详解Triton内存屏障(Memory Barrier)机制,带你掌握多线程环境下的内存同步策略,确保程序正确性与性能平衡。 ## 内存屏障基础:为什么需要同步机制 在GPU多线程编程中,当多个线程同时访问共享内存时,可能出现数据竞争(Data Race)问题。Triton内存屏障(Memo...

gitblog_00411的博客 484

彻底搞懂Linux内存屏障:smp_rmb与smp_wmb如何拯救并发数据安全

在多处理器系统中,CPU为了提高性能会对指令进行重排序,这可能导致共享数据访问出现意想不到的问题。想象一下:当两个CPU核心同时读写同一个变量时,没有同步机制的情况下,结果可能完全违背直觉。Linux内核提供的内存屏障(Memory Barrier)就是解决这类问题的关键机制,其中`smp_rmb`(读内存屏障)和`smp_wmb`(写内存屏障)是最常用的两种。本文将通过内核源码实例,带你深入理解...

gitblog_00389的博客 626

【JUC 进阶实战】撕下 JIT 的伪装!用大白话彻底搞懂 volatile 与内存屏障

💣 常见误区:以为加了 volatile 就能高枕无忧,保证绝对的线程安全了?大错特错!定义一个,然后开 10 个线程去狂跑count++,最后问你结果是不是 10。结果绝对小于 10!volatile无法保证原子性!count++在 CPU 眼里根本不是一步,而是三步(去主内存读值 -> 在 CPU 里加 1 -> 写回主内存)。volatile只能保证你“读”的那一瞬间是最新的,但如果你读完还没来得及写回去,另一个线程也读了,你们俩算出来的结果就会互相覆盖。

2401_87395400的博客 404

【高性能嵌入式编程必修课】:彻底搞懂循环缓冲区读写指针的原子操作与内存屏障

掌握C语言循环缓冲区的读写指针同步,解决多线程数据竞争与溢出问题。适用于嵌入式通信、生产者消费者模型,通过原子操作与内存屏障实现高效同步,提升系统稳定性与性能,值得收藏。

LearnFlow的博客 471

Java volatile关键字详解:从JMM到内存屏障,彻底搞懂并发可见性与有序性

在多线程编程中,如何确保共享变量的修改对其他线程实时可见,是并发设计的基础难题。Java内存模型(JMM)规定了线程工作内存与主内存之间的交互方式,而volatile关键字正是JMM提供的一种轻量级同步机制。它通过内存屏障和缓存一致性协议,保证变量的跨线程可见性,同时禁止指令重排序,从而维护程序的有序性。与synchronized相比,volatile无锁且开销更低,但无法保证复合操作的原子性。理解volatile的核心语义,需要掌握JMM、Happens-Before规则以及内存屏障的底层原理。在实际工程

weixin_30231327的博客 66

彻底搞懂顺序一致性与 SeqCst:硬件总线锁(Bus Lock)与全内存屏障(MFENCE)的真正开销

顺序一致性概念最早由图灵奖得主 Leslie Lamport 于 1979 年提出。它的正式数学定义极其直观:一个系统的执行结果是顺序一致的,当且仅当:所有处理器执行操作的结果,都与这些操作按照某种**单一的全局顺序(Single Total Order)**依次执行的结果相同,并且每个独立处理器上的操作在全局顺序中都严格遵循其程序指定的次序(Program Order)。所有线程的读写操作都交替打成一条唯一的线性时间轴;所有线程看到的全局内存变更次序完全一模一样。

no1coder的博客 857
上一篇: cache line提升程序性能
下一篇: 彻底搞懂内存屏障(下)
OS技术解密
博客等级 码龄16年 3760粉丝 189原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值