Atomic Reference Counting Atomic Reference Counting multithreading multithreading

Atomic Reference Counting


You probably overestimate the threadsafety a shared_ptr provides.

The essence of atomic ref counting is to ensure that if two different instances of a shared_ptr (that are managing the same object) are accessed/modified, there will be no race condition. However, shared_ptr doesn't ensure thread safety, if two threads access the same shared_ptr object (and one of them is a write). One example would be e.g. if one thread dereferences the pointer, while the other resets it.
So about the only thing shared_ptr gurantees is that there will be no double delete and no leak as long as there is no race on a single instance of a shared_ptr (It also doesn't make accesses to the object it points to threadsafe)

As a result, also creating a copy of a shared_ptr is only safe, if there is no other thread that could delete/reset it at the same time (you could also say, it is not internally synchronized). This is the scenario you describe.

To repeat it once more: Accessing a single shared_ptr instance from multiple threads where one of those accesses is a write to the pointer is still a race condition.

If you want to e.g. copy a std::shared_ptrin a threadsafe manner, you have to ensure that all loads and stores happen via std::atomic_... operations which are specialized for shared_ptr.


Your scenario is not possible because Thread B should have been created with an incremented refcount already. Thread B should not be incrementing the ref count as the first thing it does.

Let's say Thread A spawns Thread B. Thread A has the responsibility to increment the ref count of the object BEFORE creating the thread, to guarantee thread safety. Thread B then only has to call release when it exits.

If Thread A creates Thread B without incrementing the ref count, bad things might happen as you've described.


The implementation doesn't provide or require such a guarantee, avoidance of the behavior you are describing is predicated on the proper management of counted-references, usually done through a RAII class such as std::shared_ptr. The key is to entirely avoid passing by raw pointer across scopes. Any function which stores or retains a pointer to the object must take a shared pointer so that it can properly increment the ref count.

void f(shared_ptr p) {   x(p); // pass as a shared ptr   y(p.get()); // pass raw pointer}

This function was passed a shared_ptr so the refcount was already 1+. Our local instance, p, should have bumped the ref_count during copy-assignment. When we called x if we passed by value we created another ref. If we passed by const ref, we retained our current ref count. If we passed by non-const ref then it's feasible that x() released the reference and y is going to be called with null.

If x() stores/retains the raw pointer, then we may have a problem. When our function returns the refcount might reach 0 and the object might be destroyed. This is our fault for not correctly maintaining ref count.

Consider:

template<typename T>void test(){    shared_ptr<T> p;    {        shared_ptr<T> q(new T); // rc:1        p = q; // rc:2    } // ~q -> rc:1    use(p.get()); // valid} // ~p -> rc:0 -> delete

vs

template<typename T>void test(){    T* p;    {        shared_ptr<T> q(new T); // rc:1        p = q; // rc:1    } // ~q -> rc:0 -> delete    use(p); // bad: accessing deleted object}