Using an acquire fence before an atomic load

  Kiến thức lập trình

Usually, an acquire fence is used after an atomic load:

if (flag.load(std::memory_order_relaxed)) {
  std::atomic_thread_fence(std::memory_order_acquire);
  // Read some non-atomic data here.
}

However, does it ever make sense to use an acquire fence before an atomic load?

In the example below, a buffer data and an atomic boolean flag are shared between threadA and threadB.
The data buffer initially contains all-zero. threadA will write non-zero to it, but before it does so, it sets the flag to true. threadB reads from the data buffer. After reading, it checks the atomic flag.

#include <atomic>

uint8_t data[4096] = {};
std::atomic<bool> flag = false;

void threadA() {
  flag.store(true, std::memory_order_relaxed);
  std::atomic_thread_fence(std::memory_order_release);
  for (uint8_t &byte : data) {
    byte = 1;
  }
}

void threadB() {
  bool found_non_zero = false;
  for (uint8_t byte : data) {
    if (byte) {
      found_non_zero = true;
    }
  }
  std::atomic_thread_fence(std::memory_order_acquire);
  bool flag_observed = flag.load(std::memory_order_relaxed);
  if (found_non_zero) {
    ASSERT_TRUE(flag_observed);
  }
}

Can it be guranteed that, if threadB has read any non-zero data from the buffer, then it must observe that the flag was set to true (i.e. the assertion always succeeds)?

The hope is that the release fence in threadA prevents reordering the flag.store to after the writes to data, and that the acquire fence in threadB prevents reordering the flag.load to before the reads of data.

However, according to the standard on fence-to-fence synchronization, acquire fence has an effect only if it appears after an atomic load; likewise, release fence has an effect only if it appears before an atomic store. This suggests that data should be atomic while the flag should not, which seems wrong. If the fences indeed have no effect, what modification to the code would ensure that the assertions always succeed?

New contributor

cyb0124 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

1

The data needs to be atomic, not the flag, but the loads and stores on the data can be relaxed due to the fence.

The acquire fence ensures that if you read an atomic before the fence, then the non-atomic read after the fence will be at least as new as the atomic that you read before the fence

You need the release fence to guarantee this relationship, as the memory model specifies that the observation on the atomic is what will establish a happens-before relationship between change of the non-atomic data on one thread and its observation on the other thread, the relationship is established between the two threads. (in practice one thread has to flush its cache to the other thread)

In other words, the fence ensures that the non-atomic data that will be loaded, has caught up with the atomic data that was already loaded.

your code has it the other way around, you want the atomic data to catch-up with the non-atomic data, the problem is that the fence doesn’t work here, therefore all your loads and stores are not ordered.

LEAVE A COMMENT