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
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.