对于任何std::atomic<T>
,其中T是基本类型:
如果我std::memory_order_acq_rel
用于fetch_xxx
操作,std::memory_order_acquire
用于load
操作,std::memory_order_release
用于store
操作盲目(我的意思是比如重置这些功能的默认内存顺序)
std::memory_order_seq_cst
(用作默认值)进行任何已声明的操作相同?std::memory_order_seq_cst
不同?答案 0 :(得分:63)
原子操作的C ++ 11内存排序参数指定了对排序的约束。如果您使用std::memory_order_release
进行存储,并且来自另一个线程的加载使用std::memory_order_acquire
读取值,则来自第二个线程的后续读取操作将看到第一个线程存储到任何内存位置的任何值在商店发布之前,或以后存储到任何这些内存位置。
如果存储和后续加载都是std::memory_order_seq_cst
,那么这两个线程之间的关系是相同的。你需要更多的线程才能看出差异。
e.g。 std::atomic<int>
变量x
和y
,最初均为0。
主题1:
x.store(1,std::memory_order_release);
主题2:
y.store(1,std::memory_order_release);
主题3:
int a=x.load(std::memory_order_acquire); // x before y
int b=y.load(std::memory_order_acquire);
主题4:
int c=y.load(std::memory_order_acquire); // y before x
int d=x.load(std::memory_order_acquire);
如上所述,商店与x
和y
之间没有任何关系,因此很有可能在第3个主题中看到a==1
,b==0
,{线程4中的{1}}和c==1
。
如果所有内存排序都更改为d==0
,则会强制在商店之间对std::memory_order_seq_cst
和x
进行排序。因此,如果主题3看到y
和a==1
,那么这意味着b==0
的商店必须位于商店之前x
,所以如果主题4看到y
,意味着c==1
的商店已经完成,那么y
的商店也必须已经完成,因此我们必须x
。
实际上,在任何地方使用d==1
都会为加载或存储或两者增加额外开销,具体取决于您的编译器和处理器体系结构。例如x86处理器的常用技术是使用std::memory_order_seq_cst
指令而不是XCHG
指令用于MOV
存储,以便提供必要的排序保证,而对于std::memory_order_seq_cst
a plain { {1}}就足够了。在具有更宽松的内存架构的系统上,开销可能更大,因为普通的加载和存储具有更少的保证。
内存排序很难。我在my book中投入了几乎整整一章。
答案 1 :(得分:7)
内存排序可能非常棘手,而错误的效果通常非常微妙。
所有内存排序的关键点在于它保证发生了什么&#34;而不是将要发生的事情。例如,如果您将某些内容存储到几个变量(例如x = 7; y = 11;
),那么另一个处理器可能会在y
看到x中的值7
之前将其视为11。通过在设置x
和设置y
之间使用内存排序操作,您使用的处理器将保证x = 7;
在继续存储{{1}内容之前已写入内存}。
大多数情况下,只要最终更新了值,您的写入顺序就不重要了。但是,如果我们有一个带整数的循环缓冲区,我们会做类似的事情:
y
并且其他一些线程正在使用buffer[index] = 32;
index = (index + 1) % buffersize;
来确定已写入新值,然后我们需要将index
写入FIRST,然后32
更新后更新。否则,另一个线程可能会获得index
个数据。
这同样适用于使信号量,互斥量等工作 - 这就是为什么术语释放和获取用于内存屏障类型。
现在,old
是最严格的排序规则 - 它强制要求在处理器继续执行更多操作之前,您已写入的数据的读取和写入都会发送到内存。这比执行特定的获取或释放障碍要慢。它强制处理器确保商店和负载已经完成,而不仅仅是商店或只是加载。
这有多大差异?它高度依赖于系统结构。在某些系统上,缓存需要[部分]刷新和从一个核心发送到另一个核心的中断来说&#34;请在继续之前执行此缓存刷新工作&#34; - 这可能需要几百个周期。在其他处理器上,它只比常规内存写入慢一些。 X86非常擅长快速完成这项工作。某些类型的嵌入式处理器(某些型号 - 不确定?)例如ARM,需要在处理器中多做一些工作才能确保一切正常。