Search code examples
c++visual-c++visual-studio-2012atomicshared-ptr

atomic_load/atomic_store on std::shared_ptr in VC11 - why the global spinlock?


I'm trying to understand exactly how to manage shared pointers safely with atomic operations. Turns out VC11 (Visual studio 2012) has support for C++11 and thereby can permit read/write races on std::shared_ptr. I want to check I understood the basics, then ask something about the implementation detail of the atomic ops on std::shared_ptr in VC11.

std::shared_ptr<A> x, y, z;
x = std::make_shared<A>(args1);
y = std::make_shared<A>(args2);

Thread 1

std::shared_ptr<A> temp = std::atomic_load(&y);

Thread 2

std::atomic_store(&y, z);

Without the atomics, the race would potentially cause temp to end up having corrupted state, or Thread 2 could delete the A instance pointed to by the original y just as Thread 1 was attempting to copy and addref the shared_ptr, which would make it point to a "zombie" object.

My question regarding atomic_load and atomic_store in VC11:

I've noticed they use a spinlock that performs test-and-set on a global variable. So I wondered: why not do the test-and-set on the topmost bit of the reference counter of the shared_ptr itself? that way locks on different shared_ptr's won't contend with each other. Is there a reason this was not done?

EDIT: VS implementation of atomic_is_lock_free. Not surprising, since it IS using a spinlock for everything. Still wondering why they couldn't make it use a shared_ptr-instance-specific lock instead of a global lock.

template <class _Ty> inline
bool atomic_is_lock_free(const shared_ptr<_Ty> *)
{   // return true if atomic operations on shared_ptr<_Ty> are lock-free
    return (false);
}

Solution

  • You can't do an atomic test-and-set on the shared_ptr's ref count because the ref count is stored in the shared_ptr's control block. By the time you got around to attempting your test-and-set, another thread could have released the last shared_ptr reference and deleted the control block from under you.

    Thread 1                                  Thread 2
    Read control block address
    
                                              Decrement ref count (now 0)
                                              Delete control block
    
    Test-and-set ref count (undefined behaviour)
    

    Remember that the premise here is that multiple threads are manipulating the same shared_ptr instance. If each thread has its own instance (pointing to the same controlled object), then we have no issues and no need for atomic shared_ptr operations.