Does anybody know of a fully thread-safe shared_ptr
implementation? E.g. boost implementation of shared_ptr
is thread-safe for the targets (refcounting
I dont think this so easy, it is not enough to wrap your sh_ptr classes with a CS. It is true that if you maintain one single CS for all shared pointers it can ensure to avoid mutual the access and deletion of sh_ptr objects among different threads. But this would be terrible, one CS object for every shared pointer would be a real bottleneck. It would be suitable if every wrappable new ptr -s have different CS s' but this way we should create our CS dinamically, and ensure the copy ctors of sh_ptr classes to transmit this shared Cs. Now we arrived to the same problem: who quaranties that this Cs ptr is already deleted or not. We can be a little more smarty with volatile m_bReleased flags per instance but this way we cannot stuck the safety gaps between checking the flag and using the shared Cs. I can't see completely safe resolution for this problem. Maybe that terrible global Cs would be the minor bad as killing the app. (sorry for my english)
Adding the necessary barriers for such a fully thread-safe shared_ptr implementation would likely impact performance. Consider the following race (note: pseudocode abounds):
Thread 1: global_ptr = A;
Thread 2: global_ptr = B;
Thread 3: local_ptr = global_ptr;
If we break this down into its constituent operations:
Thread 1:
A.refcnt++;
tmp_ptr = exchange(global_ptr, A);
if (!--tmp_ptr.refcnt) delete tmp_ptr;
Thread 2:
B.refcnt++;
tmp_ptr = exchange(global_ptr, B);
if (!--tmp_ptr.refcnt) delete tmp_ptr;
Thread 3:
local_ptr = global_ptr;
local_ptr.refcnt++;
Clearly, if thread 3 reads the pointer after A's swap, then B goes and deletes it before the reference count can be incremented, bad things will happen.
To handle this, we need a dummy value to be used while thread 3 is doing the refcnt update: (note: compare_exchange(variable, expected, new) atomically replaces the value in variable with new if it's currently equal to new, then returns true if it did so successfully)
Thread 1:
A.refcnt++;
tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;
Thread 2:
B.refcnt++;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;
Thread 3:
tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, BAD_PTR))
tmp_ptr = global_ptr;
local_ptr = tmp_ptr;
local_ptr.refcnt++;
global_ptr = tmp_ptr;
You've now had to add a loop, with atomics in it in the middle of your /read/ operation. This is not a good thing - it can be extremely expensive on some CPUs. What's more, you're busy-waiting as well. You can start to get clever with futexes and whatnot - but by that point you've reinvented the lock.
This cost, which has to be borne by every operation, and is very similar in nature to what a lock would give you anyway, is why you generally don't see such thread-safe shared_ptr implementations. If you need such a thing, I would recommend wrapping a mutex and shared_ptr into a convenience class to automate locking.
You can easily do this by including a mutex object with every shared pointer, and wrapping increment/decrement commands with the lock.