内存屏障,英文名为“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