memory_order_seq_cst和memory_order_acq_rel有何不同?

时间:2012-09-09 16:26:27

标签: c++ c++11 memory-model

商店是发布操作,负载是两者的获取操作。我知道memory_order_seq_cst意味着为所有操作强加一个额外的总排序,但是我没有建立一个例子,如果所有memory_order_seq_cst都被{{1 }}

我是否会遗漏某些内容,或者差异仅仅是文档效果,即如果有人打算不使用更轻松的模型并且在约束宽松模型时使用memory_order_acq_rel,则应该使用memory_order_seq_cst

4 个答案:

答案 0 :(得分:26)

http://en.cppreference.com/w/cpp/atomic/memory_order有一个很好的示例at the bottom,仅适用于memory_order_seq_cst。基本上memory_order_acq_rel提供相对于原子变量的读写顺序,而memory_order_seq_cst提供全局读写顺序。也就是说,顺序一致的操作在所有线程中以相同的顺序可见。

这个例子归结为:

bool x= false;
bool y= false;
int z= 0;

a() { x= true; }
b() { y= true; }
c() { while (!x); if (y) z++; }
d() { while (!y); if (x) z++; }

// kick off a, b, c, d, join all threads
assert(z!=0);

z上的操作由两个原子变量保护,而不是一个,因此您不能使用获取释放语义来强制z总是递增。

答案 1 :(得分:3)

尝试仅使用获取/释放语义来构建Dekkers或Petersons算法。

这行不通,因为获取/释放语义不提供[StoreLoad]防护。

对于Dekkers算法:

flag[self]=1 <-- STORE
while(true){
    if(flag[other]==0) { <--- LOAD
        break;
    }
    flag[self]=0;
    while(turn==other);
    flag[self]=1        
}

没有[StoreLoad]围栏,商店可能会跳到负载前面,然后算法会中断。同时有2个线程会看到另一个锁是空闲的,设置自己的锁并继续。现在关键部分有2个线程。

答案 2 :(得分:2)

在x86之类的ISA上,原子映射到障碍,并且实际的机器模型包括存储缓冲区:

  • seq_cst存储区需要刷新存储缓冲区,因此该线程的后续读取将延迟到存储区全局可见之后。
  • acq_rel不会刷新存储缓冲区。普通的x86加载和存储实质上具有acq和rel语义。 (seq_cst加上具有商店转发功能的商店缓冲区。)

    但是x86原子RMW操作总是被提升为seq_cst,因为x86 asm lock前缀是一个完整的内存屏障。其他ISA可以在asm中进行宽松或acq_rel RMW。

https://preshing.com/20120515/memory-reordering-caught-in-the-act是seq_cst存储区与普通发行版存储区之间差异的示例。(实际上是mov + mfence与普通{ {1}}在x86 asm中。实际上,mov是在大多数x86 CPU上进行seq_cst存储的更有效方法,但是GCC确实使用xchg + mov


有趣的事实:AArch64的STLR发行存储指令实际上是顺序发行。在硬件中,它具有宽松或seq_cst的加载/存储以及全屏障指令。

从理论上讲,STLR只需要在下一个LDAR 之前排空存储缓冲区,而不需要在其他操作之前排空。即在下一次seq_cst加载之前。我不知道真正的AArch64硬件是否以这种方式实现它,还是在提交STLR之前就耗尽了存储缓冲区。 (无论如何,所有较早的存储区都必须在STLR之前提交,但不一定要在以后的普通加载之前提交。)

因此,通过使用LDAR / STLR将rel或acq_rel增强到seq_cst并不需要昂贵。

其他一些ISA(例如PowerPC)具有更多的障碍选择,并且可以比mfence更便宜地加固mo_relmo_acq_rel,但是它们的mo_seq_cst却不能与AArch64一样便宜; seq-cst存储需要一个完整的屏障。

答案 3 :(得分:1)

仍然使用memory_order中的定义和示例。但是将store_order_seq_cst替换为store中的memory_order_release,并将load_order_acquire替换为load。

发布 - 获取顺序保证发生的所有事情 - 在一个线程中的商店成为负载线程中的可见副作用之前。但在我们的示例中,在thread0和thread1中存储之前没有任何反应。

x.store(true, std::memory_order_release); // thread0

y.store(true, std::memory_order_release); // thread1

此外,如果没有memory_order_seq_cst,则无法保证thread2和thread3的顺序排序。你可以想象他们会变成:

if (y.load(std::memory_order_acquire)) { ++z; } // thread2, load y first
while (!x.load(std::memory_order_acquire)); // and then, load x

if (x.load(std::memory_order_acquire)) { ++z; } // thread3, load x first
while (!y.load(std::memory_order_acquire)); // and then, load y

因此,如果thread2和thread3在thread0和thread1之前执行,这意味着x和y都保持为false,因此,++ z永远不会被触及,z保持为0并且断言将触发。

但是,如果memory_order_seq_cst进入图片,它会建立所有标记的原子操作的单个总修改顺序。因此,在thread2中,x.load然后y.load;在thread3中,y.load然后x.load是肯定的事情。