TimelineCpp20Concurrency

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 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 specialized and supports specializations 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.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    Boost describes the assertions that a std::shared_ptr provides.

    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 exact 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 create atomics for integral types and floating points. This is quite convenient when you have a floating-point number 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 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 synchronization via the methods notify_one, notify_all, and wait. Notifying and waiting is with C++20 available on all partial and full specialization of std::atomic (bools, integrals, floats and pointers) and std::atomic_ref.

    Semaphores, Latches, and Barriers

    All three types are means to synchronize threads.

    Semaphores

    Semaphores are a synchronization mechanism that controls concurrent access to a shared resource. A counting semaphore, such as the one in C++20, is a special semaphore with a counter bigger than zero. The counter is initialized 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 synchronization mechanisms that enable some threads to block until a counter becomes zero.

    What are the differences between these two mechanisms to synchronize 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 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: 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, 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, Bhavith C Achar, Marco Parri Empoli, moon, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

    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
    My special thanks to SHAVEDYAKS

    Seminars

    I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

    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++
    • C++20

    Online Seminars (German)

    Contact Me

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *