C++20: Concurrency


This post concludes my overview of C++20. Today's post is about the concurrency features in the next C++ standard.



C++20 has various concurrency improvements.


The class template std::atomic_ref applies atomic operations to the referenced non-atomic object. Concurrent writing and reading of the referenced object is, therefore, no data race. The lifetime of the referenced object must exceed the lifetime of the atomic_ref. Accessing a subobject of the referenced object with an atomic_ref is not thread-safe.

Accordingly to std::atomic, std::atomic_ref can be specialised and supports specialisations for the built-in data types.

struct Counters {
    int a;
    int b;

Counter counter;
std::atomic_ref<Counters> cnt(counter);

std::atomic<std::shared_ptr<T>> and std::atomic<std::weak_ptr<T>>

std::shared_ptr is the only non-atomic data type on which you can apply atomic operations. First, let me write about the motivation for this exception. The C++ committee saw the necessity that instances of std::shared_ptr should provide a minimum atomicity guarantee in multithreading programs. What is this minimal atomicity guarantee for std::shared_ptr? The control block of the std::shared_ptr is thread-safe. This means that the increase and decrease operations of the reference-counter are atomic. You also have the guarantee that the resource is destroyed exactly once.

The assertions that a std::shared_ptr provides, are described by Boost.

  1. A shared_ptr instance can be “read” (accessed using only constant operations) simultaneously by multiple threads.
  2. Different shared_ptr instances can be “written to” (accessed using mutable operations such as operator= or reset) simultaneously by multiple threads (even when these instances are copies, and share the same reference count underneath).

With C++20 we get two new smart pointers: std::atomic<std::shared_ptr<T>> and std::atomic<std::weak_ptr<T>>.

Floating Point Atomics

In addition to C++11, you can not only create atomics for integral types but also for floating points. This is quite convenient when you have a floating-point number, which is concurrently incremented by various threads. With a floating-point atomic, you don't need to protect the floating pointer number.

Waiting on Atomics

std::atomic_flag is an atomic boolean. It has a clear and a set state. For simplicity reasons, I call the clear state false and the set state true. Its clear method enables you to set its value to false. With the test_and_set method, you can set the value to true and return the previous value. There is no method to ask for the current value. This will change with C++20. With C++20, a  std::atomic_flag has a test method.

Additionally, std::atomic_flag can be used for thread synchronisation via the methods notify_one, notify_all, and wait. Notifying and waiting is with C++20 available on all partial and full specialisation of std::atomic (bools, integrals, floats and pointers) and std::atomic_ref.

Semaphores, Latches and Barriers

All three types are means to synchronise threads.


Semaphores are a synchronisation mechanism used to control concurrent access to a shared resource. A counting semaphore such as the on in C++20, is a special semaphore which has a counter that is bigger than zero. The counter is initialised in the constructor. Acquiring the semaphore decreases the counter and releasing the semaphore increases the counter. If a thread tries to acquire the semaphore when the counter is zero, the thread will block until another thread increments the counter by releasing the semaphore.

Latches and Barriers

Latches and barriers are simple thread synchronisation mechanisms which enable some threads to block until a counter becomes zero.

What are the differences between these two mechanisms to synchronise threads? You can use a std::latch only once, but you can use a std::barrier more than once. A std::latch is useful for managing one task by multiple threads; a std::barrier is useful for managing repeated tasks by multiple threads. Additionally, a std::barrier can adjust the counter in each iteration.

The following code snippet is from the Proposal N4204.


void DoWork(threadpool* pool) {

    latch completion_latch(NTASKS);   // (1)
    for (int i = 0; i < NTASKS; ++i) {
        pool->add_task([&] {          // (2)

        // perform work
        completion_latch.count_down();// (4)
        })};                          // (3)
    // Block until work is done
    completion_latch.wait();          // (5)


The std::latch completion_latch is in its constructor set to NTASKS (line 1). The thread pool executes NTASKS (lines 2 - 3) jobs. At the end of each job (line 4), the counter is decremented. Line 5 is the barrier for the thread running the function DoWork and hence for the small workflow. This thread is blocked until all tasks have been finished.


std::jthread stands for joining thread. In addition to std::thread from C++11, std::jthread can automatically join the started thread and can be interrupted.

Here is the non-intuitive behaviour of std::thread. If a std::thread is still joinable, std::terminate is called in its destructor. A thread thr is joinable if either thr.join() nor thr.detach() was called.

// threadJoinable.cpp

#include <iostream>
#include <thread> int main(){ std::cout << std::endl; std::cout << std::boolalpha; std::thread thr{[]{ std::cout << "Joinable std::thread" << std::endl; }}; std::cout << "thr.joinable(): " << thr.joinable() << std::endl; std::cout << std::endl; }


When executed, the program terminates.


Both executions of the program terminate. In the second run, the thread thr has enough time to display its message: “Joinable std::thread”.

In the next example, I replace the header <thread> with "jthread.hpp" and, therefore, use std::jthread from the upcoming C++20 standard.


// jthreadJoinable.cpp

#include <iostream>
#include "jthread.hpp" int main(){ std::cout << std::endl; std::cout << std::boolalpha; std::jthread thr{[]{ std::cout << "Joinable std::jthread" << std::endl; }}; std::cout << "thr.joinable(): " << thr.joinable() << std::endl; std::cout << std::endl; }


Now, the thread thr automatically joins in its destructor such as in this case if still joinable.


What's next?

I provided in the last four posts an overview of the new features in C++20. After the overview, let me dive into the details. My next post is about concepts.



Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Wolfgang Gärtner,  Louis St-Amour, Stephan Roslen, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Avi Kohn, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, and Peter Ware.


Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, and Sudhakar Belagurusamy. 



I'm happy to give online-seminars or face-to-face seminars world-wide. Please call me if you have any questions.

Bookable (Online)


Standard Seminars 

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.


Contact Me

Modernes C++,



Tags: C++20

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code


Today 8443

Yesterday 6382

Week 8443

Month 175277

All 5472381

Currently are 161 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments