C++ atomics using memory_order_acquire without a matching memory_order_release

huangapple go评论62阅读模式
英文:

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 through compare_exchange_weak followed by a release store to count through fetch_add
  • in thread 2: an acquire load from count followed by a relaxed load from state

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 through compare_exchange_weak followed by a release store to count through fetch_add
  • in thread 2: an acquire load from count followed by a relaxed load from state

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.

huangapple
  • 本文由 发表于 2023年2月8日 10:46:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/75380942.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定