atomic operations for shared_ptr ?
Bronek Kozicki
brok@spamcop.net
Sun Aug 25 11:39:00 GMT 2013
On 25/08/2013 09:34, Federico Terraneo wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> On 08/24/2013 08:58 PM, Bronek Kozicki wrote:
>> On 24/08/2013 18:51, Jonathan Wakely wrote:
>>> On 24 August 2013 18:45, Bronek Kozicki wrote:
>>>>
>>>> what are the current plans for supporting atomic_* free
>>>> functions for shared_ptr, as defined in C++11 (20.6.2 "Header
>>>> <memory> synopsis")? These would be very useful for passing
>>>> shared_ptr objects between threads, esp. if there are multiple
>>>> readers and single writer.
>>>
>>> I've been thinking about it recently and am probably just going
>>> to implement it with a single global mutex used for all
>>> shared_ptr instances.
>>
>>
>> erm, but that would be even worse than using mutexes as in my
>> example code ....
>
> A while ago I was looking for a lock-free algorithm for reference
> counted smart pointers, and found this:
> http://www.drdobbs.com/atomic-reference-counting-pointers/184401888.
The problem with implementation of atomic_load and atomic_store is not
reference counters, these have been atomic for a long time already. And
are guaranteed to be so by C++11 , so the following is safe to be
executed on many threads, concurrently:
shared_ptr<Data> data() { return data_; } // data_ owned by thread1
thread2:
auto a1 = data();
thread3:
auto a2 = data();
// etc.
Each thread atomically bumps up the reference counter (typically single
assembly instruction), and there is very little space for performance
improvements too.
The problem is with accessing the shared_ptr at the same moment when it
is being reset by another thread, to a new pointee. This is not
guaranteed to be thread-safe and thus either separate synchronization is
needed, or one has to use atomic_load, atomic_store etc. overloads for
shared_ptr, if they are implemented.
Trouble implementing those is that, in the typical implementation of
shared pointer with reference count (both boost and libstdc++ happen to
be such), two pointers have to be updated atomically: one is a pointer
to actual data (_M_ptr in shared_ptr here) and the other is pointer to
reference counters (_Sp_counted_base<_Lp>* encapsulated in _M_refcount
in shared_ptr here). Since changing the object layout would break ABI,
we are not at freedom to change this, even if it made some efficient
lockfree algorithm available. The implementation of atomic_load,
atomic_store etc. has to ensure that, once one data member (eg _M_ptr)
has been read or updated, the other (eg _M_refcount) will be read (or
updated) in state consistent with the first one.
I do not quite see how the instructions you point to could help here,
they seem to be a crutch in PowerPC architecture for the lack of
read-modify-write instructions (in 2004). For more modern architectures
we do have those, for single memory location. The example you point to
does not deal with two memory locations either, as in example routine
aSwap the operation is not actually atomic in respect to both r1 and r2
(i.e. r2 could have been modified while update to r1 was being
performed, and the operation would update r2 nevertheless - or as the
author says "second memory location (arg r2) must not be subject to
contention").
Actually, our problem can be easily solved on architectures with
optimistic synchronisation primitives (since they enable atomic updates
of multiple memory locations), but currently only one processer type has
the necessary instructions, it is Intel TSX (only some processors of
Haswell family). Before this becomes more popular (and supported by gcc,
e.g. via intrinsics) we simply need a way to efficiently lock _M_ptr and
_M_refcount before reading or writing those inside implementation of
atomic_load, etc. overloads for shared_ptr.
B.
More information about the Libstdc++
mailing list