重新排序原子读取

Re-ordering Atomic Reads

本文关键字:读取 排序 新排序      更新时间:2023-10-16

我正在研究一种多线程算法,该算法读取两个共享的原子变量:

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*/
}

该算法的不变量是在读取a之前应该先读取b

问题是,编译器(比如GCC)能否重新排列指令的顺序,使ab之前读取?使用显式内存围栏可以实现这一点,但我想了解的是,两个原子负载可以重新排序吗。

此外,在了解了Herb Sutter演讲中的获取/释放语义之后(http://herbsutter.com/2013/02/11/atomic-weapons-the-c-memory-model-and-modern-hardware/),我理解顺序一致的系统确保了获取(如加载)和发布(如存储)之间的顺序。在两次获取(如两次装载)之间进行排序如何?

编辑:添加有关代码的更多信息:考虑两个线程T1&T2执行:

T1:读取b的值,休眠

T2:更改a的值,返回

T1:唤醒并读取a(新值)的新值

现在,考虑重新订购的情况:

int a_local =a; int b_local = b;

T1:读取a的值,休眠

T2:更改a的值,返回

T1:对a的数值变化一无所知。

问题是"像GCC这样的编译器能否重新排序两个原子负载`

memory_order_acquire:的说明

在该加载之前,当前线程中的任何内存访问都不能重新排序。

由于加载b时的默认内存顺序是最强的memory_order_seq_cst,因此在从b读取之前,不能对从a读取的内容进行重新排序。

即使是较弱的内存订单,如下面的代码所示,也提供了相同的保证:

int b_local = b.load(std::memory_order_acquire);
int a_local = a.load(std::memory_order_relaxed);

是的,它们可以重新排序,因为一个订单与另一个订单没有什么不同,并且您没有施加任何约束来强制任何特定的订单。这几行代码之间只有一种关系:int b_local = b;int a_local = a;之前被排序,但由于您的代码中只有一个线程,并且两行是独立的,因此第三行代码的哪一行首先完成(无论那一行可能是什么)是完全无关的,因此编译器可能会毫无疑问地对其重新排序。

所以,如果你需要强制一些你需要的特定订单:

  1. 2个以上执行线程

  2. 在这些线程中的两个操作之间建立之前发生的关系。

以下是__atomic_base在调用assignment:时所做的操作

  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. "

所以,如果我读对了,它确实"限制代码运动",我读到的意思是防止重新排序。但我可能误解了文件。

是的,我认为除了几个优化之外,它还可以进行重新排序。请检查以下资源:原子与非原子操作

如果您仍然担心这个问题,请尝试使用互斥锁,这肯定会阻止内存重新排序。