Professional Documents
Culture Documents
Atomic Smart Pointers: Half Thread-Safe
Atomic Smart Pointers: Half Thread-Safe
22 February 2017
Tweet
Contents[Show]
C++20 will have atomic smart pointers. To be exactly, we will get
a std::atomic_shared_ptr and a std::atomic_weak_ptr. But why? std::shared_ptr
and std::weak_ptr are already thread-safe. Sort of. Let me dive into the details.
Before I start, I want to make a short detour. This detour should only emphasise how import it is that
the std::shared_ptr has a well-defined multithreading semantic and you know and use it. From
the multithreading point of view, std::shared_ptr is this kind of data structures you will not use in
multithreading programs. They are by definition shared and mutable; therefore they are the ideal
candidates for data races and hence for undefined behaviour. At the other hand there is the
guideline in modern C++: Don't touch memory. That means, use smart pointers in multithreading
programs.
Half thread-safe
I often have the question in my C++ seminars: Are smart pointers thread-safe? My defined answer is
yes and no. Why? A std::shared_ptr consists of a control block and its resource. Yes, the
control block is thread-safe; but no, the access to the resource is not thread-safe. That means,
modifying the reference counter is an atomic operation and you have the guarantee that the
resource will be deleted exactly once. These are all guarantees a std::shared_ptr gives you.
The assertion that a std::shared_ptr provides, are described by Boost.
To make the two statements clear, let me show a simple example. When you copy
a std::shared_ptr in a thread, all is fine.
The lambda-function binds the std::shared_ptr ptr by reference (1). Therefore the assignment
(2) is a race condition on the resource and the program has undefined behaviour.
Admittedly that was not so easy to get. std::shared_ptr requires special attention in a
multithreading environment. They are very special. They are the only non-atomic data types in C+
for which atomic operations exist.
std::atomic_is_lock_free(std::shared_ptr)
std::atomic_load(std::shared_ptr)
std::atomic_load_explicit(std::shared_ptr)
std::atomic_store(std::shared_ptr)
std::atomic_store_explicit(std::shared_ptr)
std::atomic_exchange(std::shared_ptr)
std::atomic_exchange_explicit(std::shared_ptr)
std::atomic_compare_exchange_weak(std::shared_ptr)
std::atomic_compare_exchange_strong(std::shared_ptr)
std::atomic_compare_exchange_weak_explicit(std::shared_ptr)
std::atomic_compare_exchange_strong_explicit(std::shared_ptr)
For the details, have a look at cppreference.com. Now it is quite easy to modify a by reference
bounded std::shared_ptr in a thread-safe way.
The update of the std::shared_ptr ptr (1) is thread-safe. All is well? NO. Finally, we come to
the new atomic smart pointers.
public:
concurrent_stack() =default;
~concurrent_stack() =default;
class reference {
shared_ptr<Node> p;
public:
reference(shared_ptr<Node> p_) : p{p_} { }
T& operator* () { return p->t; }
T* operator->() { return &p->t; }
};
All changes that are necessary to compile the program with a C++11 compiler are red. The
implementation with atomic smart pointers is a lot easier and hence less error-prone. C++20 does
not permit it to use a non-atomic operation on a std::atomic_shared_ptr.