0%

内存屏障

内存屏障,英文名为“memory barrier”,又可被称为“内存栅栏”,“内存栅障”等,用来指代内存同步原语。

一、内存屏障的作用

内存屏障的作用:

  • 禁止重排序
  • 刷新缓存到主存
  • 使缓存失效

备注

  • 内存屏障可具有上述作用,但不一定需要具备上述所有作用

二、内存屏障的存在形式

本节内容属于笔者自我理解,如有错误,敬请指正。

内存屏障的存在形式分为“独立型”和“绑定型”两种:

  • 独立型。“内存屏障指令”独立于“业务指令”存在,比如“在X86指令集中的lfence指令”
  • 绑定型。“内存屏障指令”与“业务指令”绑定在一起,比如“在X86指令集中,给一条业务汇编指令加上LOCK汇编指令前缀,则其既为汇编指令,也为内存屏障指令”

对于“独立型内存屏障指令”,与“业务指令”之间可能被插入其他指令;而对于“绑定型内存屏障指令”,与“其绑定的业务指令”之间不允许被插入其他指令。

三、不同指令/语言层级的内存屏障

对应于指令/语言层级,内存屏障一般分为3类:机器指令集中的内存屏障,汇编指令集中的内存屏障,程序语言中的内存屏障。

3.1、机器指令集中的内存屏障

不同的机器指令集具有不同的“内存屏障机器指令”。

3.2、汇编指令集中的内存屏障

不同的汇编指令集具有不同的“内存屏障汇编指令”。
比如在X86指令集中,内存屏障汇编指令有:

  • lfence,sfence,mfence
  • 给汇编指令加上LOCK汇编指令前缀便使得具有内存屏障效果

3.3、程序语言中的内存屏障

不同的程序语言具有不同的“内存屏障语言指令”,“内存屏障语言指令”是一个逻辑虚概念,不像“内存屏障机器指令”和“内存屏障汇编指令”是客观实概念。编译器编译或者解释器解释时根据“语言关键词”和“语言设计机制”达成语言语句禁止重排序插入“内存屏障汇编指令”效果,等价于存在一个“内存屏障语言指令”。

备注

  • JVM语言字节码形式中的“内存屏障字节码指令”也是类似的逻辑虚概念

3.3.1、Java中的内存屏障

在Java程序语言中,有4种内存屏障。

内存屏障名称 使用示例 描述
LoadLoad Load1;LoadLoad;Load2 确保Load1数据的装载执行完成之后再执行Load2及所有后续装载指令的装载
StoreStore Store1;StoreStore;Store2 确保Store1数据对其他处理器可见(即刷新到主存)执行完成之后再执行Store2及所有后续存储指令的存储
LoadStore Load1;LoadStore;Store2 确保Load1数据装载执行完成之后再执行Store2及所有后续存储指令的刷新到主存
StoreLoad Store1;StoreLoad;Load2 确保Store1数据对其他处理器变得可见(即刷新到主存)执行完成之后再执行Load2及所有后续装载指令的装载。
StoreLoad会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令,这个特性使得本内存屏障同时具有其他3个内存屏障的效果,故被称为“全能型”内存屏障

上述内存屏障都是逻辑虚概念,但经过编译/解释后最终会映射到汇编指令集/机器指令集中客观实概念的内存屏障。因此,接下来看似针对“Java中内存屏障”的讨论,实则针对最终所映射到的汇编指令集/机器指令集中的内存屏障:

  • StoreLoad内存屏障被大多现代的多处理器支持,其他3个内存屏障则不然
  • 执行StoreLoad内存屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到主存中

四、最终形态

相应于“程序语言 -> 汇编语言 -> 机器语言”的编译/解释过程,存在“内存屏障语言指令 -> 内存屏障汇编指令 -> 内存屏障机器指令”的内存屏障编译/解释过程。
我们知道,编译/解释得到的汇编指令/机器指令,跟具体平台相关,这自然也涵盖内存屏障,故而编译/解释得到的内存屏障汇编指令/机器指令也跟具体平台相关。特别需要注意的是,在有些平台上,内存屏障汇编指令/机器指令可能会被优化消除掉,比如“在X86体系架构中,除了“StoreLoad”内存屏障所映射到的内存屏障汇编指令/机器指令需要保留外,其他内存屏障都无需保留,因为相应的重排序在该体系架构中根本不会发生”。


参考文献

[1]《Java并发编程的艺术》
[2]http://read.pudn.com/downloads100/doc/project/409708/IA-32%BE%ED3%A3%BA%CF%B5%CD%B3%B1%E0%B3%CC%D6%B8%C4%CF[123457%2011%2012].pdf

您的支持将鼓励我继续分享!