In my last post, I introduced latches in C++20. A latch enables its threads to wait until a counter becomes zero. Additionally to a latch, its big sibling barrier can be used more than once. Today, I write about barriers and present atomic smart pointers.
If you are not familiar with std::latch, read my last post: Latches in C++20.
There are two differences between a
std::latch and a
std::latch is useful for managing one task by multiple threads; a
std::barrier helps manage repeated tasks by multiple threads. Additionally, a
std::barrier enables you to execute a function in the so-called completion step. The completion step is the state when the counter becomes zero. Immediately after the counter becomes zero, the so-called completion step starts. In this completion step, a callable is invoked. The
std::barrier gets its callable in its constructor. A callable unit (short callable) behaves like a function. Not only are these named functions but also function objects or lambda expressions.
The completion step performs the following steps:
- All threads are blocked.
- An arbitrary thread is unblocked and executes the callable.
- If the completion step is done, all threads are unblocked.
The following table presents you with the interface of a
call bar.arrive_and_drop() call means essentially that the counter is decremented by one for the next phase. The following program
fullTimePartTimeWorkers.cpp halves the number of workers in the second phase.
This workflow consists of two kinds of workers: full-time workers (1) and part-time workers (2). The part-time worker works in the morning, and the full-time worker in the morning and the afternoon. Consequently, the full-time workers call
workDone.arrive_and_wait() (lines (3) and (4)) two times. On the contrary, part-time works call
workDone.arrive_and_drop() (5) only once. This
workDone.arrive_and_drop() call causes the part-time worker to skip the afternoon work. Accordingly, the counter has in the first phase (morning) the value 6, and in the second phase (afternoon) the value 3.
Now to something I missed in my posts on atomics.
Atomic Smart Pointers
std::shared_ptr consists of a control block and its resource. The control block is thread-safe, but access to the resource is not. This means modifying the reference counter is an atomic operation, and you have the guarantee that the resource is deleted exactly once. These are the guarantees
std::shared_ptr given you.
On the contrary, it is crucial that a
std::shared_ptr has well-defined multithreading semantics. At first glance, the use of a
std::shared_ptr does not appear to be a sensible choice for multithreaded code. It is, by definition, shared and mutable and is the ideal candidate for non-synchronized read and write operations and hence for undefined behavior. On the other hand, there is a guideline in modern C++: Don’t use raw pointers. Consequently, you should use smart pointers in multithreading programs when you want to model shared ownership.
The proposal N4162 for atomic smart pointers directly addresses the deficiencies of the current implementation. The deficiencies boil down to these three points: consistency, correctness, and performance.
- Consistency: the atomic operations
std::shared_ptrare the only ones for a non-atomic data type.
- Correctness: the usage of global atomic operations is quite error-prone because the correct usage is based on discipline. It is easy to forget to use an atomic operation – such as using
ptr = localPtrinstead of
std::atomic_store(&ptr, localPtr). The result is undefined behaviour because of a data race. If we used an atomic smart pointer instead, the type system would not allow it.
- Performance: the atomic smart pointers have a significant advantage over the free
atomic_* functions. The atomic versions are designed for the particular use case and can internally have a
std::atomic_flagas a kind of cheap spinlock. Designing the non-atomic versions of the pointer functions to be thread-safe would be overkill if used in a single-threaded scenario. They would have a performance penalty.
The correctness argument is probably the most important one. Why? The answer lies in the proposal. The proposal presents a thread-safe singly linked list that supports insertion, deletion, and searching of elements. This singly linked list is implemented in a lock-free way.
All changes required to compile the program with a C++11 compiler are marked in red. The implementation of atomic smart pointers is a lot easier and hence less error-prone. C++20’s type system does not permit it to use a non-atomic operation on an atomic smart pointer.
Proposal N4162 proposed the new types
std::atomic_weak_ptr as atomic smart pointers. By merging them in the mainline ISO C++ standard, they became partial template specialization of std::atomic:
Consequently, the atomic operations for
std::shared_ptr<T> are deprecated with C++20.
With C++20, threads can be cooperatively interrupted. Let me show you in my next, what that means.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, and Bhavith C Achar.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
|My special thanks to Embarcadero|
|My special thanks to PVS-Studio|
|My special thanks to Tipi.build|
|My special thanks to Take Up Code|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,