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);
}
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.
Munging 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With