ktqq99
ktqq99

Reputation: 35

C++ atomics using memory_order_acquire without a matching memory_order_release

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)?

Upvotes: 1

Views: 317

Answers (1)

Brian Bi
Brian Bi

Reputation: 119044

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.

Upvotes: 2

Related Questions