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); } 32 Answers
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.
4Munging the top bit of the reference count would require code that deals with the reference count as a counter to ignore that top bit. That is, it would make the most common uses slower in order to provide a minor speed increase in less common cases.
1