27

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

Acquire意味着在获取栅栏之后出现的任何内存访问都不能重新排序到栅栏之前。

释放意味着在释放栅栏之前出现的任何内存访问都不能重新排序到栅栏之后。

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

澄清一下,C++11<atomic>库允许您以两种方式指定内存栅栏:您可以将栅栏指定为原子操作的额外参数,例如:

x.load(std::memory_order_acquire);

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

x.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);

我不明白的是,鉴于上述acquire和release的定义,为什么C++ 11专门将acquireload关联,将releasestore关联?是的,我已经看到了许多示例,这些示例展示了如何使用获取/加载和释放/存储来在线程之间同步,但总的来说,获取栅栏(防止语句后内存重新排序)和释放的想法似乎是栅栏(在语句之前防止内存重新排序)与加载和存储的想法是正交的。

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

x.store(10, std::memory_order_acquire);

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

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

4

3 回答 3

31

假设我写了一些数据,然后我写了一个数据现在准备好的指示。当务之急是没有其他线程看到数据准备就绪的指示,而不会看到数据本身的写入。因此,先前的写入不能超过该写入。

假设我读到一些数据已经准备好。在看到数据已准备好的读取之后,我发布的任何读取都必须发生。因此,后续读取不能落后于该读取。

因此,当您执行同步写入时,您通常需要确保您之前执行的所有写入对于看到同步写入的任何人都是可见的。而且,当您执行同步读取时,通常必须在同步读取之后执行您之后执行的任何读取。

或者,换句话说,获取通常是您可以获取或访问资源的读取,并且后续的读取和写入不得移动到它之前。发布通常是写你已经完成了资源,之前的写不能移到它之后。

于 2016-04-29T20:37:51.020 回答
3

(部分答案纠正了问题早期的一个错误 。David Schwartz 的回答已经很好地涵盖了您要问的主要问题。Jeff Preshing关于获取/发布的文章也是另一个很好的阅读材料。)


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

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

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

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

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

Jeff Preshing 的文章与此处相关:


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

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

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

如果第二个存储不是原子的,那么任何阅读器都不太可能安全地与任何东西同步,从而在没有数据竞争 UB 的情况下可以观察到值。

(尽管在破解使用普通非对象作为有效负载的 SeqLock 时确实遇到了释放围栏atomic的用例,以允许编译器进行优化。但这是一种特定于实现的行为,取决于了解 std:: atomic stuff 为真正的 CPU 编译。例如,请参阅Implementing 64 bit atomic counter with 32 bit atomics。)

于 2021-12-02T02:10:57.717 回答
-4

std::memory_order_acquirefence 仅确保在 fence 之前的任何加载操作之前,fence之后的所有加载操作都不会重新排序,因此无法确保在执行加载后 store 对其他线程可见。这就是为什么不支持 store 操作的原因,可能需要实现 store 的获取。memory_order_acquire memory_order_acquirememory_order_seq_cst

作为替代方案,您可以说

x.store(10, std::memory_order_releaxed);
x.load(std::memory_order_acquire);  // this introduce a data dependency

以确保所有负载在商店之前不会重新排序。同样,栅栏在这里不起作用。

此外,原子操作中的内存顺序可能比内存栅栏便宜,因为它只确保相对于原子指令的顺序,而不是栅栏前后的所有指令。

有关详细信息,另请参阅正式描述解释

于 2016-05-04T16:17:58.197 回答