TimelineCpp20

An Improved Thread with C++20

std::jthread stands for joining thread. In addition to std::thread (C++11), std::jthread automatically joins in its destructor and can cooperatively be interrupted. Read this post to know why std::jthread should be your first choice.

 TimelineCpp20

The following table gives you a concise overview of the functionality of std::jthread.

jthread

 

For additional details, please refer to cppreference.com. When you want to read more posts about std::thread, here are they: my post about std::thread.

First, why do we need an improved thread in C++20? Here is the first reason.

Automatically Joining

This is the non-intuitive behavior of std::thread. If a std::thread is still joinable, std::terminate is called in its destructor. A thread thr is joinable if neither thr.join() nor thr.detach() was called. Let me show what that means.

 

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.

     

    // threadJoinable.cpp
    
    #include <iostream>
    #include <thread>
    
    int main() {
        
        std::cout << '\n';
        std::cout << std::boolalpha;
        
        std::thread thr{[]{ std::cout << "Joinable std::thread" << '\n'; }};
        
        std::cout << "thr.joinable(): " << thr.joinable() << '\n';
        
        std::cout << '\n';
        
    }
    

     

    When executed, the program terminates when the local object thr goes out of scope.

    threadJoinable

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

    In the next example, I use std::jthread from the C++20 standard.

    // jthreadJoinable.cpp
    
    #include <iostream>
    #include <thread>
    
    int main() {
        
        std::cout << '\n';
        std::cout << std::boolalpha;
        
        std::jthread thr{[]{ std::cout << "Joinable std::thread" << '\n'; }};
        
        std::cout << "thr.joinable(): " << thr.joinable() << '\n';
        
        std::cout << '\n';
        
    }
    

     

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

    jthreadJoinable

    But this is not all that std::jthread  provides additionally to std::thread. A std::jthread can be cooperatively interrupted. I already presented the general ideas of cooperative interruption in my last post: Cooperative Interruption of a Thread in C++20.

    Cooperative Interruption of a std::jthread

    To get a general idea, let me present a simple example.

    // interruptJthread.cpp
    
    #include <chrono>
    #include <iostream>
    #include <thread>
    
    using namespace::std::literals;
    
    int main() {
        
        std::cout << '\n';
        
        std::jthread nonInterruptable([]{                           // (1)
            int counter{0};
            while (counter < 10){
                std::this_thread::sleep_for(0.2s);
                std::cerr << "nonInterruptable: " << counter << '\n'; 
                ++counter;
            }
        });
        
        std::jthread interruptable([](std::stop_token stoken){     // (2)
            int counter{0};
            while (counter < 10){
                std::this_thread::sleep_for(0.2s);
                if (stoken.stop_requested()) return;               // (3)
                std::cerr << "interruptable: " << counter << '\n'; 
                ++counter;
            }
        });
        
        std::this_thread::sleep_for(1s);
        
        std::cerr << '\n';
        std::cerr << "Main thread interrupts both jthreads" << '\n';
        nonInterruptable.request_stop();
        interruptable.request_stop();                              // (4)
        
        std::cout << '\n';
        
    }
    

     

    In the main program, I start the two threads nonInterruptable and interruptable (lines 1)and 2). Unlike in the thread nonInterruptable , the thread interruptable gets a std::stop_token and uses it in line (3) to check if it was interrupted: stoken.stop_requested(). In case of a stop request, the lambda function returns, and the thread ends. The call interruptable.request_stop() (line 4) triggers the stop request. This does not hold for the previous call nonInterruptable.request_stop() . The call has no effect.

    interruptJthread

    To complete my post, with C++20, you can also cooperatively interrupt a condition variable.

    New wait Overloads for std::condition_variable_any

    Before I write about std::condition_variable_any, here are my post about condition variables

    The three wait variations wait, wait_for, and wait_until of the std::condition_variable_any get new overloads. These overloads take a std::stop_token.

    template <class Predicate>
    bool wait(Lock& lock,  
              stop_token stoken,
              Predicate pred);
    
    template <class Rep, class Period, class Predicate>
    bool wait_for(Lock& lock, 
                  stop_token stoken, 
                  const chrono::duration<Rep, Period>& rel_time, 
                  Predicate pred);
                    
    template <class Clock, class Duration, class Predicate>
    bool wait_until(Lock& lock, 
                    stop_token stoken,
                    const chrono::time_point<Clock, Duration>& abs_time, 
                    Predicate pred);
    

     

    These new overloads need a predicate. The presented versions ensure getting notified if a stop request for the passed std::stop_token stoken is signaled. They return a boolean that indicates whether the predicate evaluates to true. This returned boolean is independent of whether a stop was requested or whether the timeout was triggered.

    After the wait calls, you can check if a stop request occurred.

    cv.wait(lock, stoken, predicate);
    if (stoken.stop_requested()){
        // interrupt occurred
    }
    

     

    The following example shows the usage of a condition variable with a stop request.

    // conditionVariableAny.cpp
    
    #include <condition_variable>
    #include <thread>
    #include <iostream>
    #include <chrono>
    #include <mutex>
    #include <thread>
    
    using namespace std::literals;
    
    std::mutex mutex_;
    std::condition_variable_any condVar;
    
    bool dataReady;
    
    void receiver(std::stop_token stopToken) {                 // (1)
    
        std::cout << "Waiting" << '\n';
    
        std::unique_lock<std::mutex> lck(mutex_);
        bool ret = condVar.wait(lck, stopToken, []{return dataReady;});
        if (ret){
            std::cout << "Notification received: " << '\n';
        }
        else{
             std::cout << "Stop request received" << '\n';
        }
    }
    
    void sender() {                                            // (2)
    
        std::this_thread::sleep_for(5ms);
        {
            std::lock_guard<std::mutex> lck(mutex_);
            dataReady = true;
            std::cout << "Send notification"  << '\n';
        }
        condVar.notify_one();                                  // (3)
    
    }
    
    int main(){
    
      std::cout << '\n';
    
      std::jthread t1(receiver);
      std::jthread t2(sender);
      
      t1.request_stop();                                       // (4)
    
      t1.join();
      t2.join();
    
      std::cout << '\n';
      
    }
    

     

    The receiver thread (line 1) is waiting for the notification of the sender thread (line 2). Before the sender thread sends its notification (line 3), the main thread triggers a stop request in line (4). The program’s output shows that the stop request happened before the notification.

    conditionVariableAny

    What’s next?

    What happens when your write without synchronization to std::cout? You get a mess. Thanks to C++20, we have synchronized output streams.

     

     

    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, 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, 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, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.

    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 *