商店是发布操作,负载是两者的获取操作。我知道memory_order_seq_cst
意味着为所有操作强加一个额外的总排序,但是我没有建立一个例子,如果所有memory_order_seq_cst
都被{{1 }}
我是否会遗漏某些内容,或者差异仅仅是文档效果,即如果有人打算不使用更轻松的模型并且在约束宽松模型时使用memory_order_acq_rel
,则应该使用memory_order_seq_cst
?
答案 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_rel
或mo_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是肯定的事情。