原文地址 作者:Martin Thompson 译者:一粟 校对:无叶,方腾飞
本文我将和大家讨论并发编程中最基础的一项技术:内存屏障或内存栅栏,也就是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术。
CPU使用了很多优化技术来实现一个目标:CPU执行单元的速度要远超主存访问速度。在上一篇文章 “Write Combing (合并写)”中我已经介绍了其中的一项技术。CPU避免内存访问延迟最常见的技术是将指令管道化,然后尽量重排这些管道的执行以最大化利用缓存,从而把因为缓存未命中引起的延迟降到最小。
当一个程序执行时,只要最终的结果是一样的,指令是否被重排并不重要。例如,在一个循环里,如果循环体内没用到这个计数器,循环的计数器什么时候更新(在循环开始,中间还是最后)并不重要。编译器和CPU可以自由的重排指令以最佳的利用CPU,只要下一次循环前更新该计数器即可。并且在循环执行中,这个变量可能一直存在寄存器上,并没有被推到缓存或主存,这样这个变量对其他CPU来说一直都是不可见的。
CPU核内部包含了多个执行单元。例如,现代Intel CPU包含了6个执行单元,可以组合进行算术运算,逻辑条件判断及内存操作。每个执行单元可以执行上述任务的某种组合。这些执行单元是并行执行的,这样指令也就是在并行执行。但如果站在另一个CPU角度看,这也就产生了程序顺序的另一种不确定性。
最后,当一个缓存失效发生时,现代CPU可以先假设一个内存载入的值并根据这个假设值继续执行,直到内存载入返回确切的值。
7 |
执行单元 -> Load/Store缓冲区->L1 Cache --->L3 Cache-->内存控制器-->主存 |
9 |
+-> Write Combine缓冲区->L2 Cache ---+ |
代码顺序并不是真正的执行顺序,只要有空间提高性能,CPU和编译器可以进行各种优化。缓存和主存的读取会利用load, store和write-combining缓冲区来缓冲和重排。这些缓冲区是查找速度很快的关联队列,当一个后来发生的load需要读取上一个store的值,而该值还没有到达缓存,查找是必需的,上图描绘的是一个简化的现代多核CPU,从上图可以看出执行单元可以利用本地寄存器和缓冲区来管理和缓存子系统的交互。
在多线程环境里需要使用某种技术来使程序结果尽快可见。这篇文章里我不会涉及到 Cache Conherence 的概念。请先假定一个事实:一旦内存数据被推送到缓存,就会有消息协议来确保所有的缓存会对所有的共享数据同步并保持一致。这个使内存数据对CPU核可见的技术被称为内存屏障或内存栅栏。
内存屏障提供了两个功能。首先,它们通过确保从另一个CPU来看屏障的两边的所有指令都是正确的程序顺序,而保持程序顺序的外部可见性;其次它们可以实现内存数据可见性,确保内存数据会同步到CPU缓存子系统。
大多数的内存屏障都是复杂的话题。在不同的CPU架构上内存屏障的实现非常不一样。相对来说Intel CPU的强内存模型比DEC Alpha的弱复杂内存模型(缓存不仅分层了,还分区了)更简单。因为x86处理器是在多线程编程中最常见的,下面我尽量用x86的架构来阐述。
Store Barrier
Store屏障,是x86的”sfence“指令,强制所有在store屏障指令之前的store指令,都在该store屏障指令执行之前被执行,并把store缓冲区的数据都刷到CPU缓存。这会使得程序状态对其它CPU可见,这样其它CPU可以根据需要介入。一个实际的好例子是Disruptor中的BatchEventProcessor。当序列Sequence被一个消费者更新时,其它消费者(Consumers)和生产者(Producers)知道该消费者的进度,因此可以采取合适的动作。所以屏障之前发生的内存更新都可见了。
01 |
private volatile long sequence = RingBuffer.INITIAL_CURSOR_VALUE;
|
04 |
long nextSequence = sequence.get() + 1L;
|
09 |
final long availableSequence = barrier.waitFor(nextSequence);
|
10 |
while (nextSequence <= availableSequence)
|
12 |
event = ringBuffer.get(nextSequence);
|
13 |
boolean endOfBatch = nextSequence == availableSequence;
|
14 |
eventHandler.onEvent(event, nextSequence, endOfBatch);
|
17 |
sequence.set(nextSequence - 1L);
|
20 |
catch ( final Exception ex)
|
22 |
exceptionHandler.handle(ex, nextSequence, event);
|
23 |
sequence.set(nextSequence);
|
Load Barrier
Load屏障,是x86上的”ifence“指令,强制所有在load屏障指令之后的load指令,都在该load屏障指令执行之后被执行,并且一直等到load缓冲区被该CPU读完才能执行之后的load指令。这使得从其它CPU暴露出来的程序状态对该CPU可见,这之后CPU可以进行后续处理。一个好例子是上面的BatchEventProcessor的sequence对象是放在屏障后被生产者或消费者使用。
Full Barrier
Full屏障,是x86上的”mfence“指令,复合了load和save屏障的功能。
Java内存模型
Java内存模型中volatile变量在写操作之后会插入一个store屏障,在读操作之前会插入一个load屏障。一个类的final字段会在初始化后插入一个store屏障,来确保final字段在构造函数初始化完成并可被使用时可见。
原子指令和Software Locks
原子指令,如x86上的”lock …” 指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序。
内存屏障的性能影响
内存屏障阻碍了CPU采用优化技术来降低内存操作延迟,必须考虑因此带来的性能损失。为了达到最佳性能,最好是把要解决的问题模块化,这样处理器可以按单元执行任务,然后在任务单元的边界放上所有需要的内存屏障。采用这个方法可以让处理器不受限的执行一个任务单元。合理的内存屏障组合还有一个好处是:缓冲区在第一次被刷后开销会减少,因为再填充改缓冲区不需要额外工作了。
相关推荐
内存屏障是一种在计算机科学中用来控制指令执行顺序和内存读写的同步机制。由于现代计算机系统的CPU处理速度远超过内存访问速度,为了提高性能,CPU会采用缓存(Cache)技术,而这种技术往往会导致内存操作的重新...
### 内存屏障机制及其在Linux Kernel中的应用 #### 一、内存屏障基本概念 内存屏障(Memory Barrier),又称内存栅栏或内存围栏,是一种用于控制处理器内部内存操作顺序的机制。它确保某些类型的内存操作按指定...
优化屏障和内存屏障是内核同步的两种重要手段,用于防止编译器和处理器的优化导致的数据乱序问题。 优化屏障主要用于阻止编译器对源代码指令的重排序。在Linux内核中,`barrier()`宏就是一个典型的优化屏障实现,它...
### Linux内核内存屏障知识点详解 #### 一、引言 在现代计算机系统尤其是多处理器系统(SMP)中,为了提高性能,处理器通常会采用缓存机制来减少访问主存的时间延迟。然而,这种机制可能导致不同处理器之间数据的...
### 内存屏障访问顺序详解 #### 引言 随着技术的发展与摩尔定律的推进,处理器的速度不断提升,而内存访问速度却未能跟上这一步伐。这种差异导致内存操作成为现代处理器性能瓶颈之一。为了缓解这个问题,现代...
### Linux内核内存屏障知识点详解 #### 一、内存访问抽象模型 在现代计算机系统中,内存访问操作可能会出现乱序执行的现象。这种现象主要来源于CPU的指令流水线技术,该技术通过并行处理指令的不同阶段来提高...
Java内存屏障与JVM并发详解实用 Java内存屏障是java并发编程中的一种机制,用于确保多线程程序的正确执行。它通过强制处理器顺序执行内存操作,从而避免了内存屏障带来的问题。在本文中,我们将深入探讨Java内存...
6. 声屏障设计流程图:声屏障设计流程图包括声学设计、结构设计、景观设计等几个方面。 7. 声屏障降噪原理:声屏障的降噪原理是利用声屏障对声源附近的敏感点进行保护,阻断噪声的传播。 8. 声屏障插入损失计算:...
乱序执行和内存屏障 乱序执行是高级处理器中的一种技术,为了提高内部逻辑元件的利用率以提高运行速度,处理器通常会采用多指令发射、乱序执行等各种措施。乱序执行可以使处理器在一个指令周期内并发执行多条指令,...
内存屏障浅析,多线程编程,由于编译器的优化和缓存的使用,导致对内存的写入操作不能及时的反应出来,也就是说当完成对内存的写入操作之后,读取出来的可能是旧的内容
6. **未来趋势**:随着硬件和软件的不断发展,内存屏障的使用和优化也将面临新的挑战。谢宝友可能展望了未来内存管理技术的发展,以及内存屏障在其中的角色。 7. **最佳实践**:最后,他可能给出了在实际开发中使用...
Linux内存屏障是并行编程中一个至关重要的概念,它涉及到多核CPU的缓存一致性问题。在现代计算机系统中,由于CPU的运算速度远超内存访问速度,CPU通常会使用缓存来加速数据读写。然而,当多个CPU核心同时访问同一块...
在这个模型中,内存屏障(Memory Barrier)和重排序(Reordering)是两个关键概念,它们对并发编程的正确性和性能有着重要影响。 **重排序** 重排序是指编译器和处理器为了优化程序性能,可能会改变程序执行顺序的...
主要为大家讲解JVM内存模型|内存结构|内存屏障,他们的概念,有什么关联以及各种的功能
Linux内存屏障是并行编程领域的重要概念,它关注的是如何在多处理器系统中保持内存操作的顺序性和一致性。在进行并行编程时,尤其是在多核CPU环境下,内存访问顺序和一致性问题尤为突出。为了解决这些问题,处理器...
10. 内存屏障:内存屏障用于确保特定内存操作的顺序,防止数据竞争和乱序执行,如`mb()`(内存屏障)和`smp_mb()`(对称多处理内存屏障)。 以上只是一部分可能涵盖的内存管理内核API,实际的压缩包内容可能包括更...
内存屏障的概念内存屏障,也称为内存栅栏,是CPU或编译器用来限制特定操作的指令,它可以保证特定操作的顺序,以及保证某些数据的可见性。在Java中,volatile关键字的实现就依赖于内存屏障。内存屏障分为写屏障和读...
内存屏障是一种在多处理器系统中被广泛使用的同步机制,它确保了内存操作的顺序性,对于保证多核处理器环境下软件的正确运行至关重要。为了更好地理解内存屏障的作用和它在硬件层面的表现,我们有必要先了解CPU缓存...
4. 内存屏障:内存屏障是一种硬件层面的机制,用于保证内存操作的顺序。Java内存模型通过插入内存屏障来实现其规定的行为。 5. volatile的工作原理:volatile变量的读操作会加入读屏障,写操作会加入写屏障。这使得...
Linux内核内存模型中提到的内存屏障,包括了对特定操作的前后条件的控制,其原理是通过强制处理器在执行屏障指令前后的内存操作时,要按照特定的顺序进行,从而防止编译器和CPU的优化重新排列指令,导致程序运行结果...