对于这两个操作,存储是释放操作,加载是获取操作。我知道memory_order_seq_cst
的目的是对所有操作施加额外的总排序,但我无法构建一个示例,说明如果所有的memory_order_seq_cst
都被memory_order_acq_rel
取代,情况就不是这样。
我是否遗漏了什么,或者区别只是文档效果,也就是说,如果不打算使用更宽松的模型,那么应该使用memory_order_seq_cst
,而在约束宽松模型时使用memory_order_acq_rel
?
发布于 2012-09-10 00:45:58
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
始终递增。
发布于 2019-09-22 04:39:27
在像x86这样的ISA上,原子映射到屏障,而实际的机器模型包括一个存储缓冲区:
seq_cst
存储需要刷新存储缓冲区,因此此线程稍后的读取会被延迟,直到存储全局visible.之后再读取
acquire
或release
不必刷新存储缓冲区。正常的x86加载和存储本质上具有acq和rel语义。(seq_cst加上一个具有存储转发功能的存储缓冲区。)但是x86原子RMW操作总是被提升到seq_cst
,因为x86 lock
前缀是一个完整的内存屏障。其他ISA可以在asm中进行relaxed或acq_rel
RMW,商店端可以对以后的商店进行有限的重新排序。(但不是以使RMW看起来非原子的方式:For purposes of ordering, is atomic read-modify-write one operation or two?)
是seq_cst存储和普通发布存储之间区别的一个有启发性的例子。(在x86 asm中,它实际上是mov
+ mfence
与普通mov
。实际上,对于在大多数x86 CPU上进行seq_cst存储,xchg
是一种更有效的方法,但GCC确实使用了mov
+mfence
)
有趣的事实:AArch64的LDAR获取-加载指令实际上是一个顺序获取指令,与STLR有一个特殊的交互。直到ARMv8.3LDAPR才能执行简单的获取操作,这些操作可以与早期版本和seq_cst存储(STLR)一起重新排序。(seq_cst
加载仍然使用LDAR,因为它们使用need that interaction with STLR来恢复顺序一致性;seq_cst
和release
存储都使用STLR)。
使用STLR / LDAR,您可以获得顺序一致性,但只需在下一次LDAR之前清空存储缓冲区,而不是立即在每次seq_cst存储之后执行其他操作。我认为真正的STLR确实是这样实现的,而不是在提交AArch64之前简单地排空存储缓冲区。
通过使用LDAR / STLR将rel或acq_rel增强为seq_cst并不需要很昂贵,除非您seq_cst存储一些内容,然后seq_cst加载其他内容。那么它就和x86一样糟糕了。
其他一些ISA(如PowerPC)有更多的屏障可供选择,可以比mo_seq_cst
更便宜地增强到mo_rel
或mo_acq_rel
,但它们的seq_cst
不能像AArch64那样便宜;seq-cst商店需要一个完整的屏障。
因此,AArch64是seq_cst
存储在现场使用特殊指令或屏障指令来排空存储缓冲区的规则的一个例外。ARMv8是在C++11 / Java之后设计的,这并不是巧合,基本上seq_cst是无锁原子操作的缺省设置,所以提高它们的效率很重要。在CPU之后,架构师有几年的时间来考虑提供屏障指令或只是获取/释放与宽松加载/存储指令的替代方案。
发布于 2020-05-19 22:28:58
尝试构建仅具有获取/释放语义的Dekkers或Peterson算法。
这是行不通的,因为获取/释放语义没有提供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个线程。
https://stackoverflow.com/questions/12340773
复制相似问题