memory_order: acquire/release reversed pattern

114 views Asked by At

Might be stupid but rather being asked than regret for the rest of my life.

The standard pattern of std::atomic::load and std::atomic::store would be something like this

N.B.: assume that in all examples below the initial value for data is 0

writer:

data = 42;
sync_after_write.store(1, memory_order_release);

reader:

while (sync_after_write.load(memory_order_acquire) != 1) {}
assert(data == 42); // must hold

I'm wondering if there is a way to express the reversed pattern where sync happens before storing the data value. The idea is to ensure that the writer hasn't started modifying the data and the reader still has the original value of it.

Something along these lines

writer:

sync_before_write.store(1, /* which memory access ? */); // we want to prevent subsequent
                                                         // stores to appear before this statement
data = 42;

reader:

data_copy = data;
if (sync_before_write.load(/* which memory access ? */) == 0) { // we want to prevent previous
                                                                // loads to appear after this statement
  assert(data_copy == 0); // in other words since sync_before_write is zero then
                          // the writer hasn't started modifying the data value
                          // yet and it stays to be zero (original)
}

Edit: I should admit it was not clear from the beginning, data is NOT atomic and may require multiple cycles to read/write from/to

1

There are 1 answers

5
Jan Schultke On BEST ANSWER

You can only do this if both data and sync are std::atomic so you can use acquire/release on both. If you can't do that, you'll have to do something else.

Detecting potential tearing in data after already copying it is something C++ doesn't support, but is what the pattern does, and it is possible to hack that up in C++ in a way that compiles to working asm with real compilers for real ISAs.


To simplify your problem, you have one thread with

sync = 1;
data = 42;

... and another thread with

data_copy = data;
if (sync == 0)
    assert(data_copy == 0);

This requires only acquire/release ordering, due to two relationships:

If data = 42 synchronizes with data_copy = data, then sync = 1 happens before data_copy = data because the happens before relationship is acyclic.

An intuitive model for acquire/release is that each acquiring thread sees a certain point on the timeline of other threads' release operations. It is not possible to see release operations in reversed order. If sync == 0, then sync = 1 has not yet happened and data = 42 has not yet happened either.


Note: Perhaps you should use something like a std::mutex if you can't make both atomic.