Cur*_*ous 2 c++ multithreading memory-barriers stdatomic c++20
参考下面的代码
auto x = std::atomic<std::uint64_t>{0};
auto y = std::atomic<std::uint64_t>{0};
// thread 1
x.store(1, std::memory_order_release);
auto one = y.load(std::memory_order_seq_cst);
// thread 2
y.fetch_add(1, std::memory_order_seq_cst);
auto two = x.load(std::memory_order_seq_cst);
Run Code Online (Sandbox Code Playgroud)
这里有可能one和two都为 0 吗?
(我似乎遇到了一个错误,在上面的代码运行后,如果one和two都可以保持 0 的值,则可以解释该错误。并且排序规则太复杂,我无法弄清楚上面可以进行哪些排序。)
是的,两个负载都有可能获得0.
在线程 1 中,y.load可以“通过”,x.store(mo_release)因为它们不都是 seq_cst。ISO C++ 保证必须存在的 seq_cst 操作的全局总序仅包括 seq_cst 操作。
(就普通 CPU 的硬件/CPU 架构而言,在释放存储离开存储缓冲区之前,负载可以从一致缓存中获取值。在这种情况下,我发现根据我如何知道的方式进行推理要容易得多它针对 x86 进行编译(或通用释放和获取操作),然后应用 asm 内存排序规则。应用此推理假设正常的C++->asm 映射是安全的,并且始终至少与 C++ 内存模型一样强大。如果你能以这种方式找到合法的重新排序,你就不需要费力地通过 C++ 形式主义。但如果你不这样做,那当然不能证明它在 C++ 抽象机中是安全的。)
无论如何,要认识到的关键点是 seq_cst 操作并不像atomic_thread_fence(mo_seq_cst)- 各个seq_cst操作只需以与其他seq_cst操作交互的方式恢复/维护顺序一致性,而不是与普通的 acquire/release/acq_rel 交互。(同样,获取和释放栅栏是更强的双向障碍,与Jeff Preshing 解释的获取和释放操作不同。)
这是唯一可能的重新排序;其他可能性只是两个线程的程序顺序的交错。让商店“发生”(变得可见)最后才会产生结果0, 0。
我将oneand重命名two为r1and r2(每个线程内的本地“寄存器”),以避免编写诸如one == 0.
// x=0 nominally executes in T1, but doesn't have to drain the store buffer before later loads
auto r1 = y.load(std::memory_order_seq_cst); // T1b r1 = 0 (y)
y.fetch_add(1, std::memory_order_seq_cst); // T2a y = 1 becomes globally visible
auto r2 = x.load(std::memory_order_seq_cst); // T2b r2 = 0 (x)
x.store(1, std::memory_order_release); // T1a x = 0 eventually becomes globally visible
Run Code Online (Sandbox Code Playgroud)
这实际上可能发生在 x86 上,但有趣的是 AArch64 上不会发生。x86 可以在没有额外障碍的情况下进行发布存储(只是普通存储),并且 seq_cst 加载的编译方式与普通获取相同,只是普通加载。
在 AArch64 上,release 和 seq_cst 存储使用 STLR。seq_cst 加载使用 LDAR,它与 STLR 有特殊的交互,在最后一个 STLR 从存储缓冲区中耗尽之前不允许读取缓存。因此 ARMv8 上的 release-store / seq_cst 加载与 seq_cst store / seq_cst 加载相同。(ARMv8.3 添加了 LDAPR,通过让获取负载以不同的方式编译来允许真正的获取/释放;请参阅此问答。)
然而,这种情况也可能发生在许多使用单独屏障指令的 ISA 上,例如 ARM32:发布存储通常先使用屏障,然后使用普通存储来完成,从而防止对较早的加载/存储进行重新排序,但不会停止对以后的重新排序。如果 seq_cst 加载避免在其自身之前需要完整的屏障(这是正常情况),则存储可以在加载后重新排序。
例如,ARMv7 上的发布存储是dmb ish; str,而 seq_cst 加载是ldr; dmb ish,因此您有 str / ldr ,它们之间没有障碍。
在 PowerPC 上,由于 seq_cst load 为hwsync; ld; cmp; bc; isync,因此在加载之前有一个完整的屏障。(我认为 HeavyWeight 同步是防止 IRIW 重新排序的一部分,以阻止同一物理核心上的 SMT 线程之间的存储转发,只有当其他核心实际上变得全局可见时才能看到来自其他核心的存储。)
| 归档时间: |
|
| 查看次数: |
719 次 |
| 最近记录: |