我正在研究一种多线程算法,该算法读取两个共享原子变量:
std::atomic<int> a(10);
std::atomic<int> b(20);
void func(int key) {
int b_local = b;
int a_local = a;
/* Some Operations on a & b*/
}
算法的不变量是在阅读b
之前应该读取a
。
问题是,编译器(比方说GCC)可以重新排序指令,以便在a
之前读取b
吗?使用显式内存栅栏可以实现这一点,但我想要理解的是,可以重新排序两个原子负载。
此外,在通过Herb Sutter的谈话(http://herbsutter.com/2013/02/11/atomic-weapons-the-c-memory-model-and-modern-hardware/)获取Acquire / Release语义后,我理解顺序一致的系统确保了获取(如加载)和释放(如存储)之间的排序。如何在两次获取之间进行排序(如两次加载)?
编辑:添加有关代码的更多信息: 考虑两个线程T1和amp; T2执行:
T1:读取b
的值,休眠
T2:更改a
的值,返回
T1:唤醒并读取新值a
(新值)
现在,请考虑重新排序的情况:
int a_local =a;
int b_local = b;
T1:读取a
的值,休眠
T2:更改a
的值,返回
T1:不知道有关a
的价值变化的任何事情。
问题是“像GCC这样的编译器可以重新排序两个原子载荷吗?
答案 0 :(得分:0)
是的,它们可以重新排序,因为一个订单与另一个订单没有区别,并且您没有任何约束来强制任何特定订单。这些代码行之间只有一种关系:int b_local = b;
在 int a_local = a;
之前排序,但由于您的代码中只有一个线程,而且两行是独立的,因此它完全无关紧要对于第3行代码(无论该行可能是什么),首先完成哪一行,因此编译器可能会毫无疑问地对其进行重新排序。
因此,如果您需要强制执行某些特定订单:
2个以上执行线程
在这些线程中的两个操作之间建立发生之前的关系。
答案 1 :(得分:0)
memory_order_acquire
的
在此加载之前,当前线程中的内存访问不能重新排序。
作为加载b
的默认内存顺序是memory_order_seq_cst
,这是最强的,在a
阅读之前,无法重新排序b
。
即使是较弱的记忆订单,如下面的代码所示,提供相同的保证:
int b_local = b.load(std::memory_order_acquire);
int a_local = a.load(std::memory_order_relaxed);
答案 2 :(得分:-1)
以下是调用作业时__atomic_base正在执行的操作:
operator __pointer_type() const noexcept
{ return load(); }
_GLIBCXX_ALWAYS_INLINE __pointer_type
load(memory_order __m = memory_order_seq_cst) const noexcept
{
memory_order __b = __m & __memory_order_mask;
__glibcxx_assert(__b != memory_order_release);
__glibcxx_assert(__b != memory_order_acq_rel);
return __atomic_load_n(&_M_p, __m);
}
根据内置类的GCC文档,如__atomic_load_n:
https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html
"An atomic operation can both constrain code motion and be mapped to hardware instructions for synchronization between threads (e.g., a fence). To which extent this happens is controlled by the memory orders, which are listed here in approximately ascending order of strength. The description of each memory order is only meant to roughly illustrate the effects and is not a specification; see the C++11 memory model for precise semantics.
__ATOMIC_RELAXED
Implies no inter-thread ordering constraints.
__ATOMIC_CONSUME
This is currently implemented using the stronger __ATOMIC_ACQUIRE memory order because of a deficiency in C++11's semantics for memory_order_consume.
__ATOMIC_ACQUIRE
Creates an inter-thread happens-before constraint from the release (or stronger) semantic store to this acquire load. Can prevent hoisting of code to before the operation.
__ATOMIC_RELEASE
Creates an inter-thread happens-before constraint to acquire (or stronger) semantic loads that read from this release store. Can prevent sinking of code to after the operation.
__ATOMIC_ACQ_REL
Combines the effects of both __ATOMIC_ACQUIRE and __ATOMIC_RELEASE.
__ATOMIC_SEQ_CST
Enforces total ordering with all other __ATOMIC_SEQ_CST operations. "
所以,如果我正确地读这个,它确实会限制代码运动&#34;,我读到这意味着阻止重新排序。但我可能误解了文档。
答案 3 :(得分:-1)
是的,我认为除了几项优化之外,它还可以重新排序。 请检查以下资源: Atomic vs. Non-Atomic Operations
如果您仍然担心此问题,请尝试使用互斥锁,这肯定会阻止内存重新排序。