Reputation: 71
When I read boost atomics about an example wait-free ring buffer implementation:
I am wondering if the memory_order_acquire is necessary at
if (next_head == tail_.load(boost::memory_order_acquire))
seems memory_order_relaxed should work as well. My argument is that
value = ring_[tail];
happens-before
tail_.store(next(tail), boost::memory_order_release)
in pop() call. so we are sure data has been read before we store in push() call as
ring_[head] = value;
I pasted the whole boost example code below for easy reference. Thanks!
#include <boost/atomic.hpp>
template<typename T, size_t Size>
class ringbuffer {
public:
ringbuffer() : head_(0), tail_(0) {}
bool push(const T & value)
{
size_t head = head_.load(boost::memory_order_relaxed);
size_t next_head = next(head);
if (next_head == tail_.load(boost::memory_order_acquire))
//Could tail_.load above use boost::memory_order_relaxed?
return false;
ring_[head] = value;
head_.store(next_head, boost::memory_order_release);
return true;
}
bool pop(T & value)
{
size_t tail = tail_.load(boost::memory_order_relaxed);
if (tail == head_.load(boost::memory_order_acquire))
return false;
value = ring_[tail];
tail_.store(next(tail), boost::memory_order_release);
return true;
}
private:
size_t next(size_t current)
{
return (current + 1) % Size;
}
T ring_[Size];
boost::atomic<size_t> head_, tail_;
};
Upvotes: 2
Views: 623
Reputation: 1
As far as I can see, both tail_.load(boost::memory_order_acquire)
in push()
and head_.load(boost::memory_order_acquire)
in pop()
can be relaxed and replaced with xx.load(boost::memory_order_relaxed)
.
Upvotes: 0
Reputation: 136256
One reason is that in sequence:
if(next_head == tail_.load(boost::memory_order_acquire))
return false;
ring_[head] = value; // A non-atomic store.
memory_order_acquire
ensures that the following non-atomic store does not get reordered to precede that load of tail_
.
memory_order_relaxed
, on the other hand, does not prevent reordering, and hence is not sufficient here.
(Assuming boost::memory_order
is equivalent to std::memory_order
.)
On strongly-ordered systems — x86, SPARC TSO, IBM mainframe, etc. — release-acquire ordering is automatic for the majority of operations. No additional CPU instructions are issued for this synchronization mode; only certain compiler optimizations are affected (e.g., the compiler is prohibited from moving non-atomic stores past the atomic store-release or performing non-atomic loads earlier than the atomic load-acquire). On weakly-ordered systems (ARM, Itanium, PowerPC), special CPU load or memory fence instructions are used.
Upvotes: 1