如何解决memory_order_seq_cst 和 memory_order_release 的可能排序 使这种情况发生的重新排序
参考以下代码
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);
这里有没有可能,one
和 two
都是 0?
(我似乎遇到了一个可以解释的错误,如果 one
和 two
在上面的代码运行后都可以保持 0 的值。而且排序规则对我来说太复杂了找出上面可能的排序。)
解决方法
是的,两种负载都有可能获得 0
。
在线程 1 中,y.load
可以“通过”x.store(mo_release)
,因为它们不是 seq_cst。 ISO C++ 保证必须存在的 seq_cst 操作的全局总顺序 only 包括 seq_cst 操作。
(就普通 CPU 的硬件/cpu 架构而言,负载可以在 release-store 离开存储缓冲区之前从相干缓存中获取一个值。在这种情况下,我发现从我怎么知道它为 x86(或 to generic release and acquire operations)编译,然后应用 asm 内存排序规则。应用这个推理假设正常的 C++->asm mappings 是安全的,并且总是至少与 C++ 内存一样强大模型。如果你能用这种方式找到合法的重新排序,你就不需要费力地通过 C++ 形式主义。但如果你不这样做,那当然不能证明它在 C++ 中是安全的抽象机器。)
无论如何,要意识到的关键点是 seq_cst 操作不像 atomic_thread_fence(mo_seq_cst)
- 单个 seq_cst
操作只需以这种方式恢复/保持顺序一致性它们与其他 seq_cst
操作交互,而不是与普通的 Acquire/release/acq_rel 交互。 (同样,获取和释放栅栏是更强的双向屏障,不像获取和释放 操作 为 Jeff Preshing explains。)
使这种情况发生的重新排序
这是唯一可能的重新排序;其他可能性只是两个线程的程序顺序的交错。让商店“发生”(变得可见)最后会导致 0,0
结果。
我将 one
和 two
重命名为 r1
和 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
这可能在 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,通过让获取加载以不同的方式编译来允许真正的获取/释放;请参阅 this Q&A。)
但是,它也可能发生在许多使用单独屏障指令的 ISA 上,例如 ARM32:发布存储通常使用屏障完成,然后是普通存储,防止使用较早加载重新排序/存储,但不会停止重新排序。如果 seq_cst 加载避免在自身之前需要一个完整的屏障(这是正常情况),那么存储可以在加载后重新排序。
例如,ARMv7 上的发布存储是 dmb ish; str
,而 seq_cst 加载是 ldr; dmb ish
,因此您有 str / ldr,它们之间没有障碍。
在 PowerPC 上,由于 seq_cst 负载是 hwsync; ld; cmp; bc; isync
,所以在负载之前有一个完整的屏障。 (HeavWeight Sync 是我认为防止 IRIW 重新排序的一部分,以阻止同一物理核心上的 SMT 线程之间的存储转发,只有在其他核心实际上变得全局可见时才能看到它们。)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。