In P2300, the "1.4. Asynchronous Windows socket recv" example uses a pattern to mark completion (of setting the cancellation callback) that looks like this:
if (ready.load(std::memory_order_acquire) ||
ready.exchange(true, std::memory_order_acq_rel))
{ ... }
where ready is an std::atomic<bool>.
To me it looks that we could use just exchange:
if (ready.exchange(true, std::memory_order_acq_rel))
{ ... }
My question is: why also do load as in the first example? It's not clear to me if this is done for correctness or efficiency.
Background info:
The example in P2300 caters for the scenario where there are potentially two threads. First thread calls a C API, taking a C callback, then it needs to do some work emplacing an optional called stopCallback.
Typically the first thread will then load the ready variable, see a false, then proceed to set it to true via the exchange, see that it was still false and not execute the if block. The C callback might be called from a second thread that will load the ready and see the true value and execute the if block (the completion of the C API).
As far as I can see the option to do just exchange is sufficient, but both threads will do an exchange. With the option load followed by exchange: typically there will be a load and exchange from one thread and only a load from the other (though it might be that both threads to load and exchange).
exchange()requiring exclusive access by first checking with cheapload(). Ifload()returnstrue,exchange()is skipped due to short-circuiting.memory_order_acq_relmemory synchronization which is more expensive thanmemory_order_acquireon some platforms (but not on x86 family)if, maybe it's normal that it won't bereadymost times you check.