获取/发布与顺序一致的内存顺序

zah*_*hir 36 c++ concurrency atomic c++11

对于std::atomic<T>T是基本类型的任何地方:

如果我使用std::memory_order_acq_relfetch_xxx操作,以及std::memory_order_acquire用于load操作和std::memory_order_releasestore操作盲目(我的意思是,就像重置这些功能的默认内存排序)

  • 结果是否与我使用std::memory_order_seq_cst(用作默认值)的任何声明操作相同?
  • 如果结果相同,那么这种用法std::memory_order_seq_cst在效率方面是否与使用不同?

Ant*_*ams 65

原子操作的C++ 11内存排序参数指定了对排序的约束.如果您使用存储std::memory_order_release,并且来自另一个线程的加载读取该值,std::memory_order_acquire则第二个线程的后续读取操作将看到存储释放之前的第一个线程存储到任何存储器位置的任何值,或者稍后存储到任何这些内存位置.

如果存储和后续加载都是std::memory_order_seq_cst这两个线程之间的关系是相同的.您需要更多线程才能看到差异.

std::atomic<int>变量xy,双方最初为0.

线程1:

x.store(1,std::memory_order_release);
Run Code Online (Sandbox Code Playgroud)

线程2:

y.store(1,std::memory_order_release);
Run Code Online (Sandbox Code Playgroud)

线程3:

int a=x.load(std::memory_order_acquire); // x before y
int b=y.load(std::memory_order_acquire); 
Run Code Online (Sandbox Code Playgroud)

主题4:

int c=y.load(std::memory_order_acquire); // y before x
int d=x.load(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)

由于写的,有到店之间没有任何关系x,并y,所以还是比较可以看到a==1,b==0在螺纹3,以及c==1d==0螺纹4.

如果所有内存排序都改为,std::memory_order_seq_cst那么这将强制商店之间的订购xy.因此,如果线程3看到a==1b==0那么这意味着商店x必须是前门店y,因此,如果线程4看见c==1,这意味着商店y已完成,然后到店里x还必须完成,所以我们必须有d==1.

实际上,然后std::memory_order_seq_cst在任何地方使用都会为加载或存储或两者增加额外的开销,具体取决于您的编译器和处理器体系结构.例如,x86处理器的常用技术是使用XCHG指令而不是存储MOV指令std::memory_order_seq_cst,以便提供必要的排序保证,而对于std::memory_order_release普通的MOV就足够了.在具有更宽松的存储器架构的系统上,开销可能更大,因为普通的负载和存储具有更少的保证.

内存排序很难.我在书中花了几乎整整一章.

  • Antony,这就是问题所在,我在这里是因为你的书中的内存排序枚举值是以前使用的方式(如果有的话)它们被正确解释了......让我告诉你 - 学习任何东西都没有乐趣代码使用的东西(可能)将在稍后解释,而不会明确说明它会 (4认同)
  • 不可以."单个总订单"约束仅适用于`memory_order_seq_cst`操作.不包括具有其他存储器排序的操作,并且因此可以在不同线程中以不同顺序出现,只要满足任何其他约束. (2认同)

Mat*_*son 8

内存排序可能非常棘手,错误的效果通常非常微妙.

所有内存排序的关键点在于它保证了"已经发生了什么",而不是将要发生的事情.例如,如果您将某些内容存储到几个变量(例如x = 7; y = 11;),那么另一个处理器y在看到7x中的值之前可能会看到11 .通过在设置x和设置之间使用内存排序操作y,您使用的处理器将保证x = 7;在继续存储内容之前已写入内存y.

大多数情况下,只要最终更新了值,您的写入顺序就不是很重要.但是,如果我们有一个带整数的循环缓冲区,我们会做类似的事情:

buffer[index] = 32;
index = (index + 1)  % buffersize; 
Run Code Online (Sandbox Code Playgroud)

和其他一些线程index用来确定新值已写入,然后我们需要32写入FIRST,然后index更新AFTER.否则,其他线程可能会获取old数据.

这同样适用于使信号量,互斥量等工作 - 这就是为什么术语释放和获取用于内存屏障类型.

现在,这cst是最严格的排序规则 - 它强制要求在处理器继续执行更多操作之前,您写入的数据的读取和写入都会发送到内存.这比执行特定的获取或释放障碍要慢.它强制处理器确保商店和负载已经完成,而不仅仅是商店或只是加载.

这有多大差异?它高度依赖于系统结构.在某些系统上,缓存需要[部分]刷新并从一个核心发送到另一个核心的中断说"请在继续之前执行此缓存 - 刷新工作" - 这可能需要几百个周期.在其他处理器上,它只比常规内存写入慢一些.X86非常擅长快速完成这项工作.某些类型的嵌入式处理器(某些型号 - 不确定?)例如A​​RM,需要在处理器中多做一些工作才能确保一切正常.