具有 3 个线程的 Acqrel 内存顺序
Acqrel memory order with 3 threads
最近,我对 C++ 中的内存顺序了解得越多,它就越令人困惑。希望你能帮我澄清这一点(纯粹出于理论目的)。假设我有以下代码:
std::atomic<int> val = { 0 };
std::atomic<bool> f1 = { false };
std::atomic<bool> f2 = { false };
void thread_1() {
f1.store(true, std::memory_order_relaxed);
int v = 0;
while (!val.compare_exchange_weak(v, v | 1,
std::memory_order_release));
}
void thread_2() {
f2.store(true, std::memory_order_relaxed);
int v = 0;
while (!val.compare_exchange_weak(v, v | 2,
std::memory_order_release));
}
void thread_3() {
auto v = val.load(std::memory_order_acquire);
if (v & 1) assert(f1.load(std::memory_order_relaxed));
if (v & 2) assert(f2.load(std::memory_order_relaxed));
}
问题是:任何断言都可以是错误的吗?一方面,cppreference 声称,std::memory_order_release
禁止在线程 1-2 中交换后对两个存储进行重新排序,而线程 3 中的 std::memory_order_acquire
禁止在第一次加载之前对两个读取进行重新排序。因此,如果线程 3 看到第一个或第二个位被设置,则意味着存储到相应的布尔值已经发生并且它必须为真。
另一方面,线程 3 与释放它从 val
获取的值的任何人同步。它会发生吗(理论上如果不是在实践中)线程 3“获得”线程 2 的交换“1 -> 3”(因此 f2 load returns true),而不是“0 -> 1” " 由线程 1(因此第一个断言触发)?考虑到“重新排序”的理解,这种可能性对我来说毫无意义,但我找不到任何证据证明这不会在任何地方发生。
由于 ISO C++ 的“发布顺序”规则,任何断言都不会失败。这是提供您认为必须存在于最后一段中的保证的形式主义。
存储到 val
的唯一存储是 release-stores,设置了适当的位,在相应存储到 f1
或 f2
之后完成。因此,如果 thread_3
看到一个设置了 1 位的值,它肯定有 synchronized-with 设置相应变量的作者。
而且至关重要的是,它们都是 RMW 的每个部分,因此形成一个 release-sequence,让获取负载进入 thread_3
synchronize-with both CAS写,如果正好看到val == 3
.
(即使是 relaxed
RMW 也可以是 release-sequence 的一部分,尽管在那种情况下,在宽松的 RMW 之前不会有 happens-before 保证,仅适用于此原子变量或其他线程对此原子变量的其他释放操作。如果 thread_2
使用了 mo_relaxed
,f2
上的断言可能会失败,但它仍然无法破坏事物,因此断言f1
可能会失败。另见 and https://en.cppreference.com/w/cpp/atomic/memory_order)
如果有帮助,我认为那些 CAS 循环完全等同于 val.fetch_or(1, release)
。这绝对是编译器在具有 CAS 而不是原子 OR 原语的机器上实现 fetch_or 的方式。 IIRC,在 ISO C++ 模型中,CAS 失败只是一个负载,而不是 RMW。这并不重要;宽松的 no-op RMW 仍会传播 release-sequence.
(有趣的事实:x86 asm lock cmpxchg
始终是真正的 RMW,即使在失败时,至少在纸面上也是如此。但它也是一个完整的障碍,因此基本上与任何关于 weakly-ordered RMW 的推理无关。 )
最近,我对 C++ 中的内存顺序了解得越多,它就越令人困惑。希望你能帮我澄清这一点(纯粹出于理论目的)。假设我有以下代码:
std::atomic<int> val = { 0 };
std::atomic<bool> f1 = { false };
std::atomic<bool> f2 = { false };
void thread_1() {
f1.store(true, std::memory_order_relaxed);
int v = 0;
while (!val.compare_exchange_weak(v, v | 1,
std::memory_order_release));
}
void thread_2() {
f2.store(true, std::memory_order_relaxed);
int v = 0;
while (!val.compare_exchange_weak(v, v | 2,
std::memory_order_release));
}
void thread_3() {
auto v = val.load(std::memory_order_acquire);
if (v & 1) assert(f1.load(std::memory_order_relaxed));
if (v & 2) assert(f2.load(std::memory_order_relaxed));
}
问题是:任何断言都可以是错误的吗?一方面,cppreference 声称,std::memory_order_release
禁止在线程 1-2 中交换后对两个存储进行重新排序,而线程 3 中的 std::memory_order_acquire
禁止在第一次加载之前对两个读取进行重新排序。因此,如果线程 3 看到第一个或第二个位被设置,则意味着存储到相应的布尔值已经发生并且它必须为真。
另一方面,线程 3 与释放它从 val
获取的值的任何人同步。它会发生吗(理论上如果不是在实践中)线程 3“获得”线程 2 的交换“1 -> 3”(因此 f2 load returns true),而不是“0 -> 1” " 由线程 1(因此第一个断言触发)?考虑到“重新排序”的理解,这种可能性对我来说毫无意义,但我找不到任何证据证明这不会在任何地方发生。
由于 ISO C++ 的“发布顺序”规则,任何断言都不会失败。这是提供您认为必须存在于最后一段中的保证的形式主义。
存储到 val
的唯一存储是 release-stores,设置了适当的位,在相应存储到 f1
或 f2
之后完成。因此,如果 thread_3
看到一个设置了 1 位的值,它肯定有 synchronized-with 设置相应变量的作者。
而且至关重要的是,它们都是 RMW 的每个部分,因此形成一个 release-sequence,让获取负载进入 thread_3
synchronize-with both CAS写,如果正好看到val == 3
.
(即使是 relaxed
RMW 也可以是 release-sequence 的一部分,尽管在那种情况下,在宽松的 RMW 之前不会有 happens-before 保证,仅适用于此原子变量或其他线程对此原子变量的其他释放操作。如果 thread_2
使用了 mo_relaxed
,f2
上的断言可能会失败,但它仍然无法破坏事物,因此断言f1
可能会失败。另见
如果有帮助,我认为那些 CAS 循环完全等同于 val.fetch_or(1, release)
。这绝对是编译器在具有 CAS 而不是原子 OR 原语的机器上实现 fetch_or 的方式。 IIRC,在 ISO C++ 模型中,CAS 失败只是一个负载,而不是 RMW。这并不重要;宽松的 no-op RMW 仍会传播 release-sequence.
(有趣的事实:x86 asm lock cmpxchg
始终是真正的 RMW,即使在失败时,至少在纸面上也是如此。但它也是一个完整的障碍,因此基本上与任何关于 weakly-ordered RMW 的推理无关。 )