最理想的算法就是只有一个线程来负责对单个资源的所有写,而其它所有的线程都是读结
果。在多核环境中读取结果会要求 )). 来使得变化对运行在另外的 !23 核心上
的线程可见。
内存栅栏(Memory Barriers)
现代处理器为了获得更高的性能会做指令重排,在内存和执行单元中,指令执行、数据的
加载和存储都会被进行指令重排。处理器只需要确保程序逻辑能得到相同的结果,它不会关心
指令的执行顺序。对于单线程程序,指令重排不会有问题,但对于共享状态的多线程程序而言,
内存有序变化就变得非常重要。处理器用内存栅栏来标识对内存更新顺序敏感的代码片段,它
们确保确保指令的硬件执行顺序和内存变化在线程间的可见性。编译器可以在代码的合适位置
放置额外的软件栅栏来确保被编译代码的执行顺序,这些软件栅栏是附加在处理器自身的硬件
栅栏之上的。
现代 !23 相比内存系统来讲速度是非常快的。为了桥接其各个 !23,现代处理器使用了
复杂的缓存系统,这些缓存实际上是一些高效的独立的硬件哈希表。不同 !23 之间的缓存是通
过消息传输协议来保证一致性。另外,处理器还会使用“存储缓冲区”缓解对缓存的写压力,使
用“失效队列”确保——在写操作发生时,缓存一致性协议能快速知道失效消息
【注:实现细节
不是很了解】
。
对于此种实现方式,最近写入的数据可能处于任何存储中:在寄存器里,在存储缓冲区中,
在各级缓存中,在主存中。如果多个线程要共享这个值,那么这个值必须要按照一定的顺序对
其它线程可见,这种可见性是通过交换缓存一致性消息来协调完成的。内存栅栏可以控制这些
消息的适时产生。
读内存栅栏(()确保 !23 上的加载指令有序,当缓存发生变化时,读内存
栅栏会在失效队列上标记一个点。读内存栅栏标记点之前的写操作可以通过内存栅栏提供一致
性视图。
【注:读内存栅栏会在“失效队列”中标记一个节点,这个节点意味着
read barrier
之
前读取的所有数据都已不可靠(这也就告诉我们可能有变化发生),之后的所有
read
操作都
需要重新从内存中加载,因此之后的操作从而能够看到数据的最新变化,
barrier
之前的所有
线程的写指令和
barrier
之后的读指令就有了一个先后顺序。
Read memory barrier
使得排
在
read memory barrier
之前的写操作成为一个整体,在这个整体内部写操作的顺序是不确
定的,但这个整体形成了一个完整的视图,整个整体的结果对后面的操作是可见。】
写内存栅栏(9)用来确保 !23 的存储指令有序,写内存栅栏会在存储缓
冲区($)中标记一个点,这个标记点之前的数据变化(: 操作)会通过缓存
?" 到主存。写内存栅栏标记点之前发生的存储操作可以通过写内存栅栏提供有序性视图。
读写(,##)能同时确保加载'存储操作的有序性。
【注:综上所述,实际上
read barrier
意味着
read
操作不能穿越这个
read
barrier
,
write barrier
意味着
write
操作不能穿越这个
write barrier
。执行
read barrier
的
CPU
是多线程中的消费者的角色,它通过
read barrier
能够尽快看到生产者线程的执行结果。
而执行
write barrier
的
CPU
往往充当生产者的角色,它通过
write barrier
把自己执行的结
果尽可能快得让其它线程看见。】
【注:内存栅栏的实现原理?】