Reputation: 11
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?
Upvotes: 1
Views: 126
Reputation: 18090
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 (granted that the write to the atomic was either a release or it happens after a release fence).
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.
Upvotes: 0