I'm trying to understand exactly how thread-safe, atomic reference counting works, for example as with std::shared_ptr
. I mean, the basic concept is simple, but I'm really confused about how the decref plus delete
avoids race conditions.
This tutorial from Boost demonstrates how an atomic thread-safe reference counting system can be implemented using the Boost atomic library (or the C++11 atomic library).
#include <boost/intrusive_ptr.hpp>
#include <boost/atomic.hpp>
class X {
public:
typedef boost::intrusive_ptr<X> pointer;
X() : refcount_(0) {}
private:
mutable boost::atomic<int> refcount_;
friend void intrusive_ptr_add_ref(const X * x)
{
x->refcount_.fetch_add(1, boost::memory_order_relaxed);
}
friend void intrusive_ptr_release(const X * x)
{
if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) {
boost::atomic_thread_fence(boost::memory_order_acquire);
delete x;
}
}
};
Okay, so I get the general idea. But I don't understand why the following scenario is NOT possible:
Say the refcount is currently 1
.
- Thread A: atomically decrefs the refcount to
0
. - Thread B: atomically increfs the refcount to
1
. - Thread A: calls
delete
on the managed object pointer. - Thread B: sees the refcount as
1
, accesses the managed object pointer... SEGFAULT!
I can't understand what prevents this scenario from occurring, since there is nothing preventing a data race from between the time the refcount reaches 0, and the object is deleted. Decrefing the refcount and calling delete
are two separate, non-atomic operations. So how is this possible without a lock?