C++20: Concurrency

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

 TimelineCpp20Concurrency

Concurrency

C++20 has various concurrency improvements.

std::atomic_ref<T>

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

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

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.

threadJoinable

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.

jthreadJoinable

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: Paul Baxter,  Meeting C++, Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, G Prvulovic, Reiner Eiteljörge, Reinhold Dröge, Abernitzke, Richard Ohnemus, Frank Grimm, Sakib, Broeserl, António Pina, Markus Falkner, Darshan Mody, and Sergey Agafyin.

 

Thanks in particular to:   crp4

 

   

Get your e-book at Leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages. I also included more than 120 source files.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more than 140 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 700 pages full of modern C++ and more than 260 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

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

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations

Add comment


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)

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 2378

All 3020519

Currently are 182 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments