gpt4 book ai didi

multithreading - 在这种特殊情况下,C++ 11的内存顺序保证是什么?

转载 作者:行者123 更新时间:2023-12-03 11:59:29 24 4
gpt4 key购买 nike

我正在编写一些无锁代码,并且想出了一个有趣的模式,但是我不确定在宽松的内存排序下它是否会按预期运行。

解释它的最简单方法是使用一个示例:

std::atomic<int> a, b, c;

auto a_local = a.load(std::memory_order_relaxed);
auto b_local = b.load(std::memory_order_relaxed);
if (a_local < b_local) {
auto c_local = c.fetch_add(1, std::memory_order_relaxed);
}

请注意,所有操作都使用 std::memory_order_relaxed

显然,在执行该线程的线程上,必须在评估 a条件之前完成 bif的加载。

同样,对 c的读-修改-写(RMW)操作必须在条件评估后执行(因为它是在条件条件下执行的)。

我想知道的是,此代码是否可以保证 c_local的值至少与 a_localb_local的值一样?如果是这样,给定宽松的内存顺序怎么办?控制依赖项和RWM操作是否一起充当某种获取屏障? (请注意,甚至在任何地方都没有相应的发行版。)

如果上述情况成立,我相信这个例子也应该工作(假设没有溢出)-是吗?
std::atomic<int> a(0), b(0);

// Thread 1
while (true) {
auto a_local = a.fetch_add(1, std::memory_order_relaxed);
if (a_local >= 0) { // Always true at runtime
b.fetch_add(1, std::memory_order_relaxed);
}
}

// Thread 2
auto b_local = b.load(std::memory_order_relaxed);
if (b_local < 777) {
// Note that fetch_add returns the pre-incrementation value
auto a_local = a.fetch_add(1, std::memory_order_relaxed);
assert(b_local <= a_local); // Is this guaranteed?
}

在线程1上,有一个控件依赖项,我怀疑可以保证 a总是在 b递增之前就递增了(但是它们各自并驾齐驱)。在线程2上,还有另一个控件依赖项,我怀疑可以保证在 b递增之前将 b_local加载到 a中。我还认为,从 fetch_add返回的值至少应与 b_local中观察到的值一样近,因此 assert应该保持不变。但是我不确定,因为这与通常的内存排序示例大相径庭,而且我对C++ 11内存模型的理解也不是完美的(我很难确定这些内存排序效果的原因)。任何见解将不胜感激!

更新:正如bames53在评论中有帮助地指出的那样,给定足够聪明的编译器,有可能可以在正确的情况下完全优化 if,在这种情况下,可以将放松的负载重新排序为在RMW之后发生,导致它们的值比 fetch_add返回值更新(在我的第二个示例中 assert可能触发)。但是,如果插入了 if(不是 atomic_signal_fence)而不是 atomic_thread_fence怎么办?无论进行了什么优化,编译器当然都不能忽略这一点,但是它可以确保代码的行为符合预期吗?在这种情况下,是否允许CPU进行任何重新排序?

然后,第二个示例变为:
std::atomic<int> a(0), b(0);

// Thread 1
while (true) {
auto a_local = a.fetch_add(1, std::memory_order_relaxed);
std::atomic_signal_fence(std::memory_order_acq_rel);
b.fetch_add(1, std::memory_order_relaxed);
}

// Thread 2
auto b_local = b.load(std::memory_order_relaxed);
std::atomic_signal_fence(std::memory_order_acq_rel);
// Note that fetch_add returns the pre-incrementation value
auto a_local = a.fetch_add(1, std::memory_order_relaxed);
assert(b_local <= a_local); // Is this guaranteed?

另一个更新:在阅读了到目前为止的所有答复并亲自梳理了标准之后,我认为仅使用该标准就无法证明代码是正确的。因此,有人可以提出一个符合标准并触发断言的理论系统的反例吗?

最佳答案

这个例子改变了像薄空气一样读取的行为。规范中的相关讨论在第29.3p9-11节中。由于当前版本的C11标准不能保证遵守依赖关系,因此内存模型应允许触发断言。最可能的情况是编译器优化了a_local> = 0的检查。但是,即使您用信号防护栅栏代替了该支票,CPU也可以自由地对这些指令进行重新排序。
您可以使用开源CDSChecker工具在C/C++ 11内存模型下测试此类代码示例。
您的示例的有趣问题是,要使执行违反声明,必须有一个依赖循环。更具体地说:

由于if条件,线程1中的b.fetch_add取决于同一循环迭代中的a.fetch_add。线程2中的a.fetch_add取决于b.load。对于违反声明的情况,我们必须在比T2的a.fetch_add更高的循环迭代中从b.fetch_add中读取T2的b.load。现在考虑从b.load读取的b.fetch_add并将其称为#以供将来引用。我们知道b.load取决于#,因为它从#中获取值。

我们知道#必须依赖于T2的a.fetch_add,因为T2的a.fetch_add原子会在与#相同的循环迭代中读取并更新T1的先前a.fetch_add。因此,我们知道#取决于线程2中的a.fetch_add。这使我们有一个依赖关系的循环,这很奇怪,但受C/C++内存模型允许。实际产生该循环的最可能方式是(1)编译器发现a.local始终大于0,从而打破了依赖性。然后,它可以进行循环展开,并根据需要对T1的fetch_add重新排序。

关于multithreading - 在这种特殊情况下,C++ 11的内存顺序保证是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18223161/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com