在同一个原子变量上混合使用 relaxed 和 acquire/release 访问如何影响同步?
How does mixing relaxed and acquire/release accesses on the same atomic variable affect synchronises-with?
我对 C++ 内存模型中同步关系的定义有疑问,当松弛和 acquire/release 访问混合在同一个原子变量上时。考虑以下由全局初始化程序和三个线程组成的示例:
int x = 0;
std::atomic<int> atm(0);
[thread T1]
x = 42;
atm.store(1, std::memory_order_release);
[thread T2]
if (atm.load(std::memory_order_relaxed) == 1)
atm.store(2, std::memory_order_relaxed);
[thread T3]
int value = atm.load(std::memory_order_acquire);
assert(value != 1 || x == 42); // Hopefully this is guaranteed to hold.
assert(value != 2 || x == 42); // Does this assert hold necessarily??
我的问题是 T3
中的第二个断言在 C++ 内存模型下是否会失败。请注意, 表示如果 T2
使用 load/acquire 和 store/release,则断言不会失败;如果我弄错了,请纠正我。然而,如上所述,答案似乎取决于在这种情况下如何准确定义同步关系。我被cppreference上的文字弄糊涂了,想出了以下两种可能的解读。
第二个断言失败。 atm
in T1
的存储在概念上可以理解为存储 1_release
其中 _release
是指定值存储方式的注解;同样,T2
中的存储可以理解为存储 2_relaxed
。因此,如果在 T3
returns 2
中加载,线程实际读取 2_relaxed
;因此,T3
中的负载不会 与 T1
中的存储同步,并且不能保证 T3
看到 x == 42
.但是,如果 T3
returns 1
中的加载,则 1_release
被读取,因此 T3
中的加载与 [=15] 中的存储同步=] 和 T3
保证看到 x == 42
.
第二次断言成功。如果在 T3
returns 2
中加载,则此加载在 T2
中读取 relaxed store 的副作用;但是,仅当 atm
的修改顺序包含具有释放语义的先前存储时,T2
的此存储才会出现在 atm
的修改顺序中。因此,T3
中的load/acquire与T1
中的store/release是同步的,因为在atm
.[=56的修改顺序中,后者必然在前者之前=]
乍一看,the answer to this SO question似乎暗示我的读法1是正确的。然而,这个答案似乎有微妙的不同:答案中的所有商店都是发布的,问题的症结在于看到 load/acquire 和 store/release 在一对之间建立同步线程。相比之下,我的问题是当内存顺序是异构的时候,synchronises-with 是如何定义的。
我真的希望阅读 2 是正确的,因为这会使并发推理更容易。线程T2
不读写除atm
以外的任何内存;因此,T2
本身没有同步要求,因此应该能够使用宽松的内存顺序。相比之下,T1
发布 x
并且 T3
消费它——也就是说,这两个线程相互通信,因此它们显然应该使用 acquire/release 语义。换句话说,如果解释 1 被证明是正确的,那么代码 T2
不能只考虑 T2
做了什么就可以写出来;相反,T2
的代码需要知道它不应该“干扰”T1
和 T3
之间的同步。
无论如何,了解在这种情况下标准究竟批准了什么对我来说绝对是至关重要的。
因为您在 T2 中的单独加载和存储上使用宽松排序,释放序列被破坏并且第二个断言可以触发(尽管不是在 X86 等 TSO 平台上)。
您可以通过在线程 T2 中使用 acq/rel 排序(如您建议的那样)或修改 T2 以使用原子 read-modify-write 操作(RMW)来解决此问题,如下所示:
[Thread T2]
int ret;
do {
int val = 1;
ret = atm.compare_exchange_weak(val, 2, std::memory_order_relaxed);
} while (ret != 0);
atm
的修改顺序是 0-1-2,T3 将在 1 或 2 上拾取并且断言不会失败。
T2 的另一个有效实现是:
[thread T2]
if (atm.load(std::memory_order_relaxed) == 1)
{
atm.exchange(2, std::memory_order_relaxed);
}
这里RMW本身是无条件的,必须伴随着一个if-statement & (relaxed) load来保证atm
的修改顺序是0-1或者0-1-2
如果没有 if-statement,修改顺序可能是 0-2,这会导致断言失败。 (这是有效的,因为我们知道在整个程序的其余部分中只有一个其他写入。单独的 if()
/ exchange
当然 而不是 通常等同于compare_exchange_strong
.)
在 C++ 标准中,以下引用是相关的:
[intro.races]
A release sequence headed by a release operation A on an atomic object M is a maximal contiguous subsequence
of side effects in the modification order of M, where the first operation is A, and every subsequent
operation is an atomic read-modify-write operation.
[atomics.order]
An atomic operation A that performs a release operation on an atomic object M synchronizes with an atomic
operation B that performs an acquire operation on M and takes its value from any side effect in the release
sequence headed by A.
this question 是关于 为什么 RMW 在发布序列中工作。
我对 C++ 内存模型中同步关系的定义有疑问,当松弛和 acquire/release 访问混合在同一个原子变量上时。考虑以下由全局初始化程序和三个线程组成的示例:
int x = 0;
std::atomic<int> atm(0);
[thread T1]
x = 42;
atm.store(1, std::memory_order_release);
[thread T2]
if (atm.load(std::memory_order_relaxed) == 1)
atm.store(2, std::memory_order_relaxed);
[thread T3]
int value = atm.load(std::memory_order_acquire);
assert(value != 1 || x == 42); // Hopefully this is guaranteed to hold.
assert(value != 2 || x == 42); // Does this assert hold necessarily??
我的问题是 T3
中的第二个断言在 C++ 内存模型下是否会失败。请注意,T2
使用 load/acquire 和 store/release,则断言不会失败;如果我弄错了,请纠正我。然而,如上所述,答案似乎取决于在这种情况下如何准确定义同步关系。我被cppreference上的文字弄糊涂了,想出了以下两种可能的解读。
第二个断言失败。
atm
inT1
的存储在概念上可以理解为存储1_release
其中_release
是指定值存储方式的注解;同样,T2
中的存储可以理解为存储2_relaxed
。因此,如果在T3
returns2
中加载,线程实际读取2_relaxed
;因此,T3
中的负载不会 与T1
中的存储同步,并且不能保证T3
看到x == 42
.但是,如果T3
returns1
中的加载,则1_release
被读取,因此T3
中的加载与 [=15] 中的存储同步=] 和T3
保证看到x == 42
.第二次断言成功。如果在
T3
returns2
中加载,则此加载在T2
中读取 relaxed store 的副作用;但是,仅当atm
的修改顺序包含具有释放语义的先前存储时,T2
的此存储才会出现在atm
的修改顺序中。因此,T3
中的load/acquire与T1
中的store/release是同步的,因为在atm
.[=56的修改顺序中,后者必然在前者之前=]
乍一看,the answer to this SO question似乎暗示我的读法1是正确的。然而,这个答案似乎有微妙的不同:答案中的所有商店都是发布的,问题的症结在于看到 load/acquire 和 store/release 在一对之间建立同步线程。相比之下,我的问题是当内存顺序是异构的时候,synchronises-with 是如何定义的。
我真的希望阅读 2 是正确的,因为这会使并发推理更容易。线程T2
不读写除atm
以外的任何内存;因此,T2
本身没有同步要求,因此应该能够使用宽松的内存顺序。相比之下,T1
发布 x
并且 T3
消费它——也就是说,这两个线程相互通信,因此它们显然应该使用 acquire/release 语义。换句话说,如果解释 1 被证明是正确的,那么代码 T2
不能只考虑 T2
做了什么就可以写出来;相反,T2
的代码需要知道它不应该“干扰”T1
和 T3
之间的同步。
无论如何,了解在这种情况下标准究竟批准了什么对我来说绝对是至关重要的。
因为您在 T2 中的单独加载和存储上使用宽松排序,释放序列被破坏并且第二个断言可以触发(尽管不是在 X86 等 TSO 平台上)。
您可以通过在线程 T2 中使用 acq/rel 排序(如您建议的那样)或修改 T2 以使用原子 read-modify-write 操作(RMW)来解决此问题,如下所示:
[Thread T2]
int ret;
do {
int val = 1;
ret = atm.compare_exchange_weak(val, 2, std::memory_order_relaxed);
} while (ret != 0);
atm
的修改顺序是 0-1-2,T3 将在 1 或 2 上拾取并且断言不会失败。
T2 的另一个有效实现是:
[thread T2]
if (atm.load(std::memory_order_relaxed) == 1)
{
atm.exchange(2, std::memory_order_relaxed);
}
这里RMW本身是无条件的,必须伴随着一个if-statement & (relaxed) load来保证atm
的修改顺序是0-1或者0-1-2
如果没有 if-statement,修改顺序可能是 0-2,这会导致断言失败。 (这是有效的,因为我们知道在整个程序的其余部分中只有一个其他写入。单独的 if()
/ exchange
当然 而不是 通常等同于compare_exchange_strong
.)
在 C++ 标准中,以下引用是相关的:
[intro.races]
A release sequence headed by a release operation A on an atomic object M is a maximal contiguous subsequence of side effects in the modification order of M, where the first operation is A, and every subsequent operation is an atomic read-modify-write operation.
[atomics.order]
An atomic operation A that performs a release operation on an atomic object M synchronizes with an atomic operation B that performs an acquire operation on M and takes its value from any side effect in the release sequence headed by A.
this question 是关于 为什么 RMW 在发布序列中工作。