如何解决11 是 ISO c++ 下 x86_64、arm 或其他架构的有效输出吗?
这个问题基于Can't relaxed atomic fetch_add reorder with later loads on x86,like store can? 我同意给出的答案。 在 x86 上 00 永远不会发生,因为 a.fetch_add 有一个锁前缀/完整屏障,并且加载不能在 fetch_add 以上重新排序,但在其他架构上,例如 arm/mips,它可以打印 00 >.我有两个关于 x86 和 arm 上的存储缓冲区的后续问题。
-
我的电脑(核心 i3 x86_64)从来没有得到 11,即 11 是一个有效的输出 iso c++ 中的 x86 ,所以我遗漏了什么? @Daniel Langr 证明 11 是 x86 上的有效输出。
-
现在 x86_64 有一个优势 fetch_add 作为一个完整的屏障。
-
对于 arm64 ,由于 cpu 指令重新排序,有时输出可能为 00。
-
对于 arm64 或其他一些架构,如果不重新排序,输出可以是 00 吗?。我的问题 是基于此。函数 foo 的存储缓冲区值 a.fetch_add(1) 对 bar 的 a.load() 和 b.fetch_add(1) 不可见 > 是 对 foo 的 b.load() 不可见。因此,我们无需重新排序即可获得 00。这会在 ISO C++ 下在不同的架构上发生吗?
// g++ -O2 -pthread axbx.cpp ; while [ true ]; do ./a.out | grep "00" ; done
#include<cstdio>
#include<thread>
#include<atomic>
using namespace std;
atomic<int> a,b;
int reta,retb;
void foo(){
a.fetch_add(1,memory_order_relaxed); //add to a is stored in store buffer of cpu0
//a.store(1,memory_order_relaxed);
retb=b.load(memory_order_relaxed);
}
void bar(){
b.fetch_add(1,memory_order_relaxed); //add to b is stored in store buffer of cpu1
//b.store(1,memory_order_relaxed);
reta=a.load(memory_order_relaxed);
}
int main(){
thread t[2]{ thread(foo),thread(bar) };
t[0].join(); t[1].join();
printf("%d%d\n",reta,retb);
return 0;
}
解决方法
是的,ISO C++ 允许这样做;正如丹尼尔指出的那样,一种简单的方法是在 RMW 之后,在加载之前放置一些缓慢的东西,这样在两个线程都有机会增加之前它们不会执行。这应该是显而易见的,因为它不需要任何运行时重新排序发生,只是程序顺序的简单交错。 (所以 ISO C++ 允许 11
即使使用 seq_cst。)也就是说,您可以通过单独单步执行每个线程来实现这一点。
您是否想知道如何在没有延迟循环的情况下在 x86 上创建实际演示?
尝试将原子变量放在不同的缓存行中,以便两个不同的内核可以并行写入它们。
alignas(64) std::atomic<int> a,b; // the alignas applies to each separately
将它们放在同一个缓存行中,就像默认情况下可能发生的那样,获得它们所在的缓存行所有权的核心将能够在完全加载后立即执行另一个 var 的加载-增量的屏障部分完成。完成 RMW 意味着该内核的 L1d 缓存中的缓存线已经很热。 (内核只能在通过 MESI 获得独占所有权后才能修改缓存行,这也将使其对读取有效。)
因此,一个线程的两个操作极有可能在另一个线程的任一操作之前发生。 (在 x86 asm 中,每个操作都具有与其 seq_cst 等价物相同的 asm,因此我们可以在不丢失任何内容的情况下讨论全局操作顺序。)
可能唯一能阻止这种情况发生的是在 RMW 和负载之间恰到好处的时间到达的中断。
您还提出了一个单独的问题:
如果不重新排序,输出可以是00吗?
显然没有。程序顺序的交错不能将两个加载放在任一增量之前,因此需要运行时或编译时重新排序才能创建 00
效果。
a.fetch_add(1,memory_order_relaxed); // foo1
retb=b.load(memory_order_relaxed); // foo2
b.fetch_add(1,memory_order_relaxed); // bar1
reta=a.load(memory_order_relaxed); // bar2
随意混合,不要将 foo2 放在 foo1 之前或 bar2 放在 bar1 之前。
即如果单独单步执行每个线程,则永远看不到 00
。 当然,mo_relaxed
的全部意义在于它可以重新排序。指定“无需重新排序”与“使用 seq_cst”相同。
存储缓冲区的效果是一种重新排序,specifically Store Load reordering。 mo_seq_cst
甚至可以阻止这种情况,这是 seq_cst 的一部分,也是它如此昂贵的原因,尤其是对于纯存储操作。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。