英文:
C++ atomics using memory_order_acquire without a matching memory_order_release
问题
在这种情况下,Thread 2 可能会看到比 state
的效果更新的 count
值,即在看到 compare_exchange
(A) 的效果之前可能会看到 (B) 的效果。如果您希望 Thread 2 在加载 state
(C) 时始终能够看到 (A) 的效果,那么可以将 (B) 的内存顺序改为 memory_order_release
,将 (D) 的内存顺序改为 memory_order_acquire
,并交换 (C) 和 (D) 的顺序,如下所示:
// Thread 1
while (true)
{
auto expected = state.load(memory_order_relaxed);
auto desired = get_desired(expected);
if (state.compare_exchange_weak(expected, desired, memory_order_relaxed, memory_order_relaxed)) { // (A)
count.fetch_add(1, memory_order_release); // (B)
break;
}
}
// Thread 2
auto state = state.load(memory_order_acquire); // (C)
auto count = count.load(memory_order_acquire); // (D)
这样可以确保 Thread 2 在加载 state
时能够看到 (A) 的效果,而且 (B) 和 (D) 之间的内存顺序关系也能保证正确的同步。
英文:
Is there any case where we'd want to load an atomic with memory_order_acquire
without a corresponding store to the same atomic with memory_order_release
?
For example, if I have this piece of code:
std::atomic<uint64_t> state;
std::atomic<uint64_t> count;
// Thread 1
while (true)
{
auto expected = state.load(memory_order_relaxed);
auto desired = get_desired(expected);
if (state.compare_exchange_weak(expected, desired, memory_order_relaxed, memory_order_relaxed) { <----- (A)
count.fetch_add(1, memory_order_relaxed); <---- (B)
break;
}
}
// Thread 2
auto state = state.load(memory_order_acquire); <----- (C)
auto count = count.load(memory_order_relaxed); <----- (D)
The memory_order_acquire
in Thread 2 should prevent the load of count
from being moved before the load of state
, but since we use memory_order_relaxed
everywhere else, am I correct in saying that Thread 2 may see an updated value of count
that's possibly newer than state
i.e. see the effects of (B) before it sees the effects of the compare_exchange
from (A)?
If I wanted the effect of (A) to always be visible to Thread 2 when it loads it in (C), then would I change (B) to memory_order_release
, use memory_order_acquire
in (D) and flip the order of (C) and (D)?
答案1
得分: 2
The memory_order_acquire
in Thread 2 should prevent the load of count
from being moved before the load of state
.
No. Atomic memory ordering in C++ is not defined in terms of preventing certain operations from being reordered with respect to each other. Instead, it is defined in terms of requiring certain side effects to be visible at certain points. In particular,
- if thread A performs a release-store on an atomic variable,
- and thread B performs an acquire-load on the same atomic variable,
- and thread B's acquire-load reads the value that thread A's release-store stored;
- then, all side effects performed by thread A prior to the release-store are guaranteed to be observable by thread B after the acquire-load.
If you only have acquire-loads but no release-stores, then there are no such ordering guarantees.
Am I correct in saying that Thread 2 may see the effects of (B) before it sees the effects of the compare_exchange
from (A)?
Yes.
If I wanted the effect of (A) to always be visible to Thread 2 when it loads it in (C), then would I change (B) to memory_order_release
, use memory_order_acquire
in (D) and flip the order of (C) and (D)?
Yes. Assuming I've understood you correctly, after you've performed the sequence of transformations described, we will have
- in thread 1: a relaxed store to
state
throughcompare_exchange_weak
followed by a release store tocount
throughfetch_add
- in thread 2: an acquire load from
count
followed by a relaxed load fromstate
Assuming the acquire-load from count
reads the value stored by thread 1, the relaxed load from state
in thread 2 must observe the value that was stored by thread 1, or a later value.
英文:
> The memory_order_acquire
in Thread 2 should prevent the load of count
from being moved before the load of state
No. Atomic memory ordering in C++ is not defined in terms of preventing certain operations from being reordered with respect to each other. Instead, it is defined in terms of requiring certain side effects to be visible at certain points. In particular,
- if thread A performs a release-store on an atomic variable,
- and thread B performs an acquire-load on the same atomic variable,
- and thread B's acquire-load reads the value that thread A's release-store stored;
- then, all side effects performed by thread A prior to the release-store are guaranteed to be observable by thread B after the acquire-load.
If you only have acquire-loads but no release-stores, then there are no such ordering guarantees.
> am I correct in saying that Thread 2 may [...] see the effects of (B) before it sees the effects of the compare_exchange
from (A)?
Yes.
> If I wanted the effect of (A) to always be visible to Thread 2 when it loads it in (C), then would I change (B) to memory_order_release
, use memory_order_acquire
in (D) and flip the order of (C) and (D)?
Yes. Assuming I've understood you correctly, after you've performed the sequence of transformations described, we will have
- in thread 1: a relaxed store to
state
throughcompare_exchange_weak
followed by a release store tocount
throughfetch_add
- in thread 2: an acquire load from
count
followed by a relaxed load fromstate
Assuming the acquire-load from count
reads the value stored by thread 1, the relaxed load from state
in thread 2 must observe the value that was stored by thread 1, or a later value.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论