内存栅栏:获取/加载和释放/存储

Sil*_*ler 19 c++ atomic memory-model memory-barriers c++11

我的理解std::memory_order_acquirestd::memory_order_release如下:

获取意味着获取围栏之后出现的内存访问不能重新排序到围栏之前.

释放意味着在释放围栏之前出现的内存访问不能在围栏之后重新排序.

我不明白为什么特别是对于C++ 11原子库,获取围栏与加载操作相关联,而释放围栏与存储操作相关联.

为了澄清,C++ 11 <atomic>库允许您以两种方式指定内存屏障:要么可以将fence指定为原子操作的额外参数,例如:

x.load(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)

或者您可以std::memory_order_relaxed单独使用和指定围栅,例如:

x.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)

我不明白的是,鉴于上述获取和发布的定义,为什么C++ 11特别将获取加载相关联,并与商店一起发布?是的,我已经看过许多示例,这些示例显示了如何使用获取/加载与发布/存储来在线程之间进行同步,但一般来说似乎是获取fences(防止语句后的内存重新排序)和发布的想法fences(在语句之前防止内存重新排序)与加载和存储的想法正交.

那么,为什么,例如,编译器不会让我说:

x.store(10, std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)

我意识到我可以通过使用memory_order_relaxed,然后单独的atomic_thread_fence(memory_order_acquire)声明来完成上述操作,但同样,为什么我不能直接使用存储memory_order_acquire

一个可能的用例可能是,如果我想确保某些存储,比如执行可能影响其他线程的其他语句之前x = 10发生.

Dav*_*rtz 16

假设我写了一些数据,然后我写了一个数据现已准备好的指示.至关重要的是,没有其他线程看到数据准备好的指示,看不到数据本身的写入.因此,先前的写入不能超过该写入.

假设我读到一些数据准备好了.在看到数据已经准备就绪的读取之后,我发出的任何读取都是必要的.因此后续的读取不能在读取后面移动.

因此,当您执行同步写入时,通常需要确保在查看同步写入的任何人都可以看到之前执行的所有写入.当您进行同步读取时,通常必须在同步读取之后执行任何读取操作.

或者,换句话说,获取通常是您可以读取或访问资源的读取,并且后续的读取和写入不得在它之前移动.一个版本通常写着您已完成资源,并且之前的写入不得移动到它之后.


Pet*_*des 5

(部分答案纠正了问题早期部分的错误。David Schwartz 的答案已经很好地涵盖了您要问的主要问题。Jeff Preshing 的关于获取/释放的文章也很适合阅读另一种观点。)


您为获取/释放给出的定义对于栅栏来说是错误的;它们仅适用于获取操作和释放操作,例如x.store(mo_release),不适用于std::atomic_thread_fence(mo_release)

  • 获取意味着出现在获取栅栏之后的内存访问不能被重新排序到栅栏之前。[错误,对于获取操作来说是正确的]

  • 释放意味着出现在释放栅栏之前的内存访问不能被重新排序到栅栏之后。[错误,对于发布操作来说是正确的]

它们对于栅栏来说是不够的,这就是为什么 ISO C++ 对于获取栅栏(阻止 LoadStore / LoadLoad 重新排序)和释放栅栏(LoadStore / StoreStore)有更强的排序规则。

当然,ISO C++ 没有定义“重新排序”,这意味着您正在访问一些全局一致的状态。改为 ISO C++

Jeff Preshing 的文章与此处相关:

  • 获取和释放语义(获取/释放操作,例如加载、存储和 RMW)
  • 获取和释放栅栏不按您期望的方式工作解释了为什么这些单向障碍定义对于栅栏来说是不正确且不充分的,与操作不同。(因为它会让栅栏一直重新排序到程序的一端,并使所有操作彼此无序,因为它不与操作本身绑定。)

一个可能的用例可能是,如果我想确保某些存储(例如 x = 10)发生在可能影响其他线程的其他语句执行之前。

如果该“其他语句”是来自原子共享变量的加载,则实际上需要std::memory_order_seq_cst避免 StoreLoad 重新排序。 acquire//release不会acq_rel阻止它。

如果您的意思是确保原子存储在其他原子存储之前可见,则正常方法是使第二个原子存储使用mo_release.

如果第二个存储不是原子的,则任何读取器都不太可能安全地与任何内容同步,以便在没有数据争用 UB 的情况下观察到该值。

(尽管在破解使用普通非对象作为有效负载的 SeqLock 时,您确实遇到了释放栅栏的用例atomic,以允许编译器进行优化。但这是一种特定于实现的行为,取决于了解 std::原子的东西针对真实的 CPU 进行编译。例如,请参阅使用 32 位原子实现 64 位原子计数器。)