原子写入后跟同一个变量的原子读取的屏障有多有效?

How effective a barrier is a atomic write followed by an atomic read of the same variable?

考虑以下几点:

#include <atomic>

std::atomic<unsigned> var;
unsigned foo;
unsigned bar;

unsigned is_this_a_full_fence() {
     var.store(1, std::memory_order_release);
     var.load(std::memory_order_acquire);
     bar = 5;
     return foo;
}

我的想法是 var 的虚拟加载应该防止 foo 和 bar 的后续变量访问在存储之前重新排序。

代码似乎对重新排序设置了障碍 - 至少在 x86 上,发布和获取不需要特殊的防护指令。

这是编写完整栅栏 (LoadStore/StoreStore/StoreLoad/LoadLoad) 的有效方法吗?我错过了什么?

我认为该版本创建了一个 LoadStore 和 StoreStore 障碍。 acquire 创建了一个 LoadStore 和 LoadLoad barrier。并且两个变量访问之间的依赖关系创建了 StoreLoad 障碍?

编辑:将屏障更改为全栅栏。制作代码片段 C++。

你的伪代码(不是有效的 C++)作为一个整体不是原子的。

例如,context switch could happen between the store and the load and some other thread would become scheduled (or is already running on some other core) and would then change the variable in between. Context switches and interrupts 可以在每条机器指令处发生。

Is this a valid way to code a barrier

不,不是。另请参阅 pthread_barrier_init(3p), pthread_barrier_wait(3p) 和相关函数。

您应该阅读一些 pthread tutorial(实际上,C++11 线程是它们之上的一个微小抽象)并考虑使用互斥体。

注意 std::memory_order 主要影响当前线程(以及它正在观察的内容),并且不要禁止它成为 interrupted/context-switched ...

另见 this answer

假设您 运行 在多个线程中使用此代码,使用这样的顺序是不正确的,因为原子操作不同步(参见下面的 link),因此 foobar 不受保护。

但是研究适用于单个操作的保证仍然可能具有一定的价值。
作为获取操作,var.load 不会通过 foobar 上的操作重新排序(线程间)(因此#LoadStore 和#LoadLoad,你说对了)。
但是,var.store 不受任何重新排序的保护(在此上下文中)。

#StoreLoad 重新排序可以通过标记两个原子操作来防止 seq_cst。在这种情况下,所有线程都将遵守定义的顺序(尽管仍然不正确,因为非原子不受保护)。

编辑
var.store 不受重新排序的保护,因为它充当在它之前排序的操作的单向屏障(即程序顺序较早)并且在您的代码中没有操作 在那家商店之前。
var.load 作为在它之后排序的操作的单向屏障(即 foobar)。

这是一个基本示例,说明变量 (foo) 如何受到原子 store/load 对的保护:

// thread 1
foo = 42;
var.store(1, std::memory_order_release);

// thread 2
while (var.load(std::memory_order_acquire) != 1);
assert(foo == 42);

线程 2 仅在 观察到线程 1 设置的值后继续。然后说存储已与负载同步,断言无法触发。

如需完整概述,请查看 Jeff Preshing 的 blog articles

此代码的一个主要问题是存储和后续加载到同一内存位置显然不与任何其他线程同步。在 C++ 内存模型中,竞争是未定义的行为,因此编译器可以假定您的代码没有竞争。您的负载可以观察到与存储的值不同的唯一方法是如果您参加比赛。因此,在 C++ 内存模型下,编译器可以假设加载观察存储的值。

这个精确的原子代码序列出现在我的 C++ 标准委员会论文中 no sane compiler would optimize atomics under "Redundant load eliminated". There's a longer CppCon version of this paper on YouTube

现在想象一下 C++ 不是这样的书呆子,加载/存储保证保留在那里,尽管它具有固有的活泼性质。真实世界的 ISA 提供了 C++ 不提供的此类保证。您通过获取/释放提供了与其他线程的一些先行关系,但您没有提供所有线程都同意的唯一总顺序。所以是的,这将起到围栏的作用,但它与获得顺序一致性甚至整个存储顺序不同。一些架构可能有线程以明确定义但不同的顺序观察事件。对于某些应用程序来说,这完全没问题!您需要查看 IRIW(独立写入的独立读取)以了解有关此主题的更多信息。 x86-TSO 论文专门在各种处理器中实现的临时 x86 内存模型的上下文中对其进行了讨论。