问题
In the following code, how could one ensure that ptr not incremented until after *ptr has been loaded/assigned/"extracted"?
extern int arr[some_constexpr]; // assume pre-populated
extern int* ptr; // assume points to non-atomic arr
int a = *ptr;
// want "memory barrier/fence" here
++ptr;
Would an atomic pointer ensure the correct ordering/sequencing?
#include <atomic>
extern int arr[some_constexpr];
extern std::atomic<int*> ptr;
int a = *(ptr.load());
// implicit "memory barrier" achieved here by use of atomics?
ptr.store(ptr + 1);
This relates to a lock-free queue shared between two threads. I want to ensure that the data associated with the pointer is not lost/corrupted before updating the pointer.
回答1:
When ptr
is std::atomic<int*>
, ++ptr
, or ptr++
or ptr.fetch_add(1, std::memory_order_acq_rel)
ensure that no preceding/following loads/stores get reordered past/before this operation.
++ptr
, or ptr++
are essentially ptr.fetch_add(1, std::memory_order_seq_cst)
and std::memory_order_seq_cst
is almost always an overkill (cannot give an example where it is not).
Even more efficient single reader is:
int arr[some_constexpr];
std::atomic<int*> ptr;
int* p = ptr.load(std::memory_order_acquire);
int element = *p;
ptr.store(p + 1, memory_order_release);
The above is basically how boost::lockfree::spsc_queue
is implemented.
As a side note, boost::lockfree::spsc_queue
is a true wait-free (strongest non-blocking guarantee of progress) queue. What push
/pop
operations do is 1 relaxed
load, 1 acquire
load and 1 release
store and it is fundamentally not possible to implement a single-producer-single-consumer queue faster than that (sans implementation quality defects) with FIFO order guarantee. It is often used as a benchmark for all other queues. You may like to look into it.
回答2:
According to Herb Sutter's talk (which I highly recommend for trying to understand this stuff), e.g. see at time 36:25-45:25, the atomic example given in the OP will suffice to ensure that a
is assigned before ptr
is incremented.
This is because incrementing ptr
is a store (since it is being written), and therefore a "release" given the default memory order of std::memory_order_seq_cst
(but would also apply for *_acq_rel
or *_release
memory order in this case). This means that nothing occurring before the increment/store/release of ptr
can be re-ordered to occur after the increment/store/release of ptr
, whether explicitly or implicitly by the compiler, processor, and/or cache.
No explicit memory fence is required beyond those implied by atomic load/stores, and they are actually discouraged in the talk.
来源:https://stackoverflow.com/questions/60420163/how-to-guarantee-that-load-completes-before-store-occurs