什么是内存屏障

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情
   内存屏障是指“由于编译器的优化和缓存的使用,导致对内存的写入操作不能及时的反应出来,也就是说当完成对内存的写入操作之后,读取出来的可能是旧的内容”(摘自《独辟蹊径品内核》)。(这里概念貌似不是很准确,正确的定义:为了防止编译器和硬件的不正确优化,使得对存储器的访问顺序(其实就是变量)和书写程序时的访问顺序不一致而提出的一种解决办法。 它不是一种错误的现象,而是一种对错误现象提出的解决方发----欢迎指正!!)

概念就是概念,生硬的东西,懂的人能从中悟出点什么,不懂的人还是一头雾水。不要着急,我们先给内存屏障分下类,然后挨个来研究一番,等看完这篇文章,再回来读读概念,你就懂了!

内存屏障的分类:

编译器引起的内存屏障
缓存引起的内存屏障
乱序执行引起的内存屏障
1、编译器引起的内存屏障:

我们都知道,从寄存器里面取一个数要比从内存中取快的多,所以有时候编译器为了编译出优化度更高的程序,就会把一些常用变量放到寄存器中,下次使用该变量的时候就直接从寄存器中取,而不再访问内存,这就出现了问题,当其他线程把内存中的值改变了怎么办?也许你会想,编译器怎么会那么笨,犯这种低级错误呢!是的,编译器没你想象的那么聪明!让我们看下面的代码:(代码摘自《独辟蹊径品内核》)

int flag=0;

void wait(){
while ( flag == 0 )
sleep(1000);

}

void wakeup(){
flag=1;
}
这段代码表示一个线程在循环等待另一个线程修改flag。 Gcc等编译器在编译的时候发现,sleep()不会修改flag的值,所以,为了提高效率,它就会把某个寄存器分配给flag,于是编译后就生成了这样的伪汇编代码:

void wait(){
movl flag, %eax;

while ( %eax == 0)
    sleep(1000);

}
这时,当wakeup函数修改了flag的值,wait函数还在傻乎乎的读寄存器的值而不知道其实flag已经改变了,线程就会死循环下去。由此可见,编译器的优化带来了相反的效果!

但是,你又不能说是让编译器放弃这种优化,因为在很多场合下,这种优化带来的性能是十分可观的!那我们该怎么办呢?有没有什么办法可以避免这种情况?答案必须是肯定的,我们可以使用关键字volatile来避免这种情况。

volatile int flag = 0;
这样,我们就能避免编译器把某个寄存器分配给flag了。

好,上面所描述这些,就叫做“编译器优化引起的内存屏障”,是不是懂了点什么?再回去看看概念?

2、缓存引起的内存屏障

好,既然寄存器能够引起这样的问题,那么缓存呢?我们都知道,CPU会把数据取到一个叫做cache的地方,然后下次取的时候直接访问cache,写入的时候,也先将值写入cache。

那么,先让我们考虑,在单核的情况下会不会出现问题呢?先想一下,单核情况下,除了CPU还会有什么会修改内存?对了,是外部设备的DMA!那么,DMA修改内存,会不会引起内存屏障的问题呢?答案是,在现在的体系结构中,不会。

当外部设备的DMA操作结束的时候,会有一种机制保证CPU知道他对应的缓存行已经失效了;而当CPU发动DMA操作时,在想外部设备发送启动命令前,需要把对应cache中的内容写回内存。在大多数RISC的架构中,这种机制是通过一写个特殊指令来实现的。在X86上,采用一种叫做总线监测技术的方法来实现。就是CPU和外部设备访问内存的时候都需要经过总线的仲裁,有一个专门的硬件模块用于记录cache中的内存区域,当外部设备对内存写入的时候,就通过这个硬件来判断下改内存区域是否在cache中,然后再进行相应的操作。

那么,什么时候才能产生cache引起的内存屏障呢?多CPU? 是的,在多CPU的系统里面,每个CPU都有自己的cache,当同一个内存区域同时存在于两个CPU的cache中时,CPU1改变了自己cache中的值,但是CPU2却仍然在自己的cache中读取那个旧值,这种结果是不是很杯具呢?因为没有访存操作,总线也是没有办法监测的,这时候怎么办?

对阿,怎么办呢?我们需要在CPU2读取操作之前使自己的cache失效,x86下,很多指令能做到这点,如lock前缀的指令,cpuid, iret等。内核中使用了一些函数来完成这个功能:mb(), rmb(), wmb()。用的也是以上那些指令,感兴趣可以去看下内核代码。

3、乱序执行引起的内存屏障:

我们都知道,超标量处理器越来越流行,连龙芯都是四发射的。超标量实际上就是一个CPU拥有多条独立的流水线,一次可以发射多条指令,因此,很多允许指令的乱序执行,具体怎么个乱序方法,可以去看体系结构方面的书,这里只说内存屏障。

指令乱序执行了,就会出现问题,假设指令1给某个内存赋值,指令2从该内存取值用来运算。如果他们两个颠倒了,指令2先从内存中取值运算,是不是就错了?

对于这种情况,x86上专门提供了lfence,sfence,和mfence 指令来停止流水线:

lfence:停止相关流水线,知道lfence之前对内存进行的读取操作指令全部完成

sfence:停止相关流水线,知道lfence之前对内存进行的写入操作指令全部完成

mfence:停止相关流水线,知道lfence之前对内存进行的读写操作指令全部完成

转载:http://www.spongeliu.com/clanguage/memorybarrier/

多线程-内存屏障(Memory Barriers / Fences) 是一种屏障指令,它使得CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束。也叫内存栅栏或栅栏指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性。内存屏障之前的所有写操作都要回写到主内存,内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。 阅读详情

相关推荐

什么是内存屏障?为什么需要内存屏障

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

csbmww的博客 3829

什么是内存屏障

内存屏障是一种特殊的CPU指令,用于控制内存操作的顺序,确保指令的执行顺序和数据的可见性。

qq_33326733的博客 3053

内存屏障详解

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

m0_74282605的博客 1779

内存屏障简介

为了限制性能下降,CPU被设计成在从内存 中获取数据的同时,可以执行其他指令和内存引用。这明显会导致指令 和内存引用乱序执行,并导致严重的混乱 仅仅在两个CPU之间或者CPU与设备之间存在需要交互的可能性 时,才需要内存屏障。任何代码只要能够保证没有这样的交互,这样代 码就不必使用内存屏障。但是,不使用内存屏障不代表不使用同步机制。 重排序问题无时无刻不在发生,源自三种场景: 编译器编译时的优化 处理器执行时的乱序优化 缓存同步顺序(导致可见性问题) ...

Roland_Sun的专栏 1万+

C++内存屏障

内存屏障是一种同步原语,它能确保特定的内存操作顺序,防止编译器或处理器对这些操作进行重排序。内存屏障还能保证内存操作的可见性,即一个处理器对内存的修改对其他处理器可见。内存屏障是并发编程中确保正确内存访问顺序的关键机制。在C++中,虽然不直接操作内存屏障,但通过和内存序可以实现对内存访问顺序的精确控制。理解内存屏障和内存序对于编写高性能、正确的并发代码至关重要。正确使用这些机制可以避免数据竞争和其他并发问题,同时最小化性能开销。随着现代处理器架构的发展,内存屏障的实现和性能特性也在不断变化。

m0_60773087的博客 1090

内存屏障(cpu内存屏障 与java内存屏障

CPU 内存屏障 定义 读写屏障指令 为什么会出现内存屏障 java内存屏障 java内存屏障存在意义 java中内存屏障的主要类型 LoadLoad 屏障 StoreStore 屏障 LoadStore 屏障 StoreLoad 屏障 Synchronized 锁 volatile语义中的内存屏障 final语义中的内存屏障

zzsaixuexi的博客 1364

linux内存屏障

现代计算机为了提高性能多采取乱序执行,乱序执行又分为编译乱序和执行乱序两类,前者是编译器的行为,后者是cpu运行时的行为。编译乱序可以通过编译屏障来解决,而执行乱序一般通过读内存屏障、写内存屏障以及读写内存屏障来解决。

风一样的男子(quinta)的博客 1639

内存屏障

1. 什么是内存屏障 它是一条CPU指令: a)确保一些特定操作执行的顺序; b)影响一些数据的可见性(可能是某些指令执行后的结果)。 2. 内存屏障与处理器重排序 现代的处理器使用写缓冲区来临时保存向内存写入的数据,每个处理器都有自己的缓冲区。写缓冲区可以保证指令流水线持续运行,它可以避免由于处理器停顿下来等待向内存写入数据产生的阻塞。同时,通过以批处理的方式刷新写缓冲区,以及合并写缓...

u012233832的专栏 4854

LINUX内核内存屏障

preempt_disable()和相应的preempt_enable()之间的代码是禁止内核抢占的, 通过对当前。因为b=f(a)这条指令依赖于第一条指令(a++)的运行结果, 所以。考虑这样一个系统, 它具有双CPU(1和2), 每一个CPU有一对并行的数据cache(CPU 1相应A/B,CPU 1上的写屏障是为了保证这种逻辑: 假设p == 1, 那么必然有a == 3 && b == 4.因此, 依据(1), (2)和(4), 一个无条件的LOCK跟在一个UNLOCK之后, 锁相当于一个完整的。

m0_74282605的博客 1293

java内存屏障的原理与应用

目录 1.java内存屏障 2.java内存屏障的使用 一.java内存屏障 1.1 什么是内存屏障(Memory Barrier)? 内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操作执行的顺序; b) 影响一些数据的可见性(可能是某些指令执行后的结果)。编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得...

breakout_alex的博客 1万+

Java的内存屏障

一、为什么要有内存屏障 内存屏障是为了解决因为cpu,高速缓存,主内存出现的时候,导致的可见性和重序性问题。 1、原理解释 因为计算机的运算任务需要CPU和内存相互配合共同完成,其中CPU负责逻辑计算,内存负责数据存储。但是在真正的实际开发中CPU是要与内存进行交互的,但因为内存和CPU的计算速度是有差距的,因此为了提高CPU的利用效率,现代处理器结构都加入了一层读写速度尽可能接近CPU运算速度的高速缓存来作为内存与CPU之间的缓冲:将运算需要使用的数据复制到缓存中,让CPU运算可以快速进行,计算结束

qq_48241564的博客 4245

内存屏障底层原理

内存屏障是一个很晦涩的概念,今天为了让大家彻底了解在高级并发里面的这个高级主题,我们从系统层面向大家讲解内存屏障的原理、种类、应用, 下面的内容可能牵涉的东西比较多并且比较长,但是如果你耐心的看完,并且完全的吸纳,那么你就会真正的了解内存屏障! 1 速度不对等 Cpu的速度比cpu之间的互联性能及cpu试图要访问的内存性能,都要快上几个数量级 上面展示的图显示现代处理...

m0_37561834的博客 7016

Linux 内存屏障简介

Linux,内存屏障

JiMoKuangXiangQu的专栏 1641

什么是内存屏障?,为什么需要内存屏障

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

qianji_little_boy的博客 3422

内存屏障问题

内存屏障是硬件之上、操作系统或JVM之下,对并发作出的最后一层支持。再向下是是硬件提供的支持; 向上是操作系统或JVM内存屏障作出的各种封装。内存屏障是一种标准,各厂商可能采用不同的实现。 本文仅为了帮助理解JVM提供的并发机制。首先,从volatile的语义引出可见性与重排序问题; 接下来,阐述问题的产生原理,了解为什么需要内存屏障; 然后,浅谈内存屏障的标准、厂商对内存屏障的支持,并以volatile为例讨论内存屏障如何解决这些问题; 最后,补充介绍JVM内存屏障之上作出的几个封装。 为

SayBlog 1054

JVM的四种内存屏障

文章目录1、为什么要有内存屏障1.1 解释一1.2 解释二2、硬件上面的内存屏障3、Java里面的四种内存屏障 1、为什么要有内存屏障 1.1 解释一 为了解决cpu,高速缓存,主内存的带来的的可见性和重序性问题。 我们都知道计算机运算任务需要CPU和内存相互配合共同完成,其中CPU负责逻辑计算,内存负责数据存储。CPU要与内存进行交互,如读取运算数据、存储运算结果等。由于内存和CPU的计算速度有几个数量级的差距,为了提高CPU的利用率,现代处理器结构都加入了一层读写速度尽可能接近CPU运算速度的高速缓存来

qq_35091353的博客 5450
上一篇: eclipse代码自动提示设置
下一篇: 阻塞、非阻塞、异步、同步的区别
HBella
博客等级 码龄9年 4粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值