A new Thread with C++20: std::jthread

Contents[Show]

One of the participants in my CppCon 2018 workshop asked me: "Can a std::thread be interrupted?". No, that was my answer, but this is not correct anymore. With C++20, we might get a std::jthread.

 

Let me continue my story from the CppCon 2018. During a break from my concurrency workshop, I had a chat with Nicolai (Josuttis). He asked me what I think about the new proposal P0660: Cooperatively Interruptible Joining Thread. At this point, I didn't know the proposal. Nicolai is together with Herb Sutter and Anthony Williams one of the authors of the proposal. Today's post is about the concurrent future. Here is the big picture to concurrency in current and upcoming C++.

 timeline

 

From the title of the paper Cooperatively Interruptible Joining Thread you may guess that the new thread has two new capabilities: interruptable and automatic joining. Let me first write about automatically joining.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Be part of my mentoring programs:

 

 

 

 

Do you want to stay informed about my mentoring programs: Subscribe via E-Mail.

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 either thr.join() or 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 threads terminate. In the second run, the thread thr has enough time to display its message: "Joinable std::thread".

In the following example, I replace the header <thread> with "jthread.hpp" and use std::jthread from the upcoming C++ 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::thread" << 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

Interrupt a std::jthread

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

// interruptJthread.cpp

#include "jthread.hpp"
#include <chrono>
#include <iostream>

using namespace::std::literals;

int main(){
    
    std::cout << std::endl;
    
    std::jthread nonInterruptable([]{                                   // (1)
        int counter{0};
        while (counter < 10){
            std::this_thread::sleep_for(0.2s);
            std::cerr << "nonInterruptable: " << counter << std::endl; 
            ++counter;
        }
    });
    
    std::jthread interruptable([](std::interrupt_token itoken){         // (2)
        int counter{0};
        while (counter < 10){
            std::this_thread::sleep_for(0.2s);
            if (itoken.is_interrupted()) return;                        // (3)
            std::cerr << "interruptable: " << counter << std::endl; 
            ++counter;
        }
    });
    
    std::this_thread::sleep_for(1s);
    
    std::cerr << std::endl;
    std::cerr << "Main thread interrupts both jthreads" << std:: endl;
    nonInterruptable.interrupt();
    interruptable.interrupt();                                          // (4)
    
    std::cout << std::endl;
    
}

 

I started in the main program the two threads nonInterruptable and interruptable (lines 1 and 2). In contrast to the thread nonInterruptable, the thread interruptable gets a std::interrupt_token and uses it in line 3 to check if it was interrupted: itoken.is_interrupted(). In case of an interrupt, the lambda function returns, and, therefore, the thread ends. The call interruptable.interrupt() (line 4) triggers the end of the thread. This does not hold for the previous call nonInterruptable.interrupt(), which has no effect.

interruptJthread

Here are more details to interrupt tokens, the joining threads, and condition variables.

Interrupt Tokens

An interrupt token std::interrupt_token models shared ownership and can be used to signal once if the token is valid. It provides three methods valid, is_interrupted, and interrupt.

 interrupt token fixed

If the interrupt token should be temporarily disabled, you can replace it with a default constructed token. A default constructed token is not valid. The following code snippet shows how to disable and enable a thread's capability to accept signals.

 

std::jthread jthr([](std::interrupt_token itoken){
    ...
    std::interrupt_token interruptDisabled; 
    std::swap(itoken, interruptDisabled);     // (1)       
    ...
    std::swap(itoken, interruptDisabled);     // (2)
    ...
}

 

 std::interrupt_token interruptDisabled is not valid. This means that the thread can not accept an interrupt from line (1) to (2), but after line (2), it's possible.

Joining Threads

A std::jhread is a std::thread with the additional functionality to automatically signal an interrupt and join (). To support this functionality, it has a std::interrupt_token.

jthread

New Wait Overloads for Condition Variables

The two wait variations wait_for, and wait_until of the std::condition_variable get new overloads. They take a std::interrupt_token.

template <class Predicate>
bool wait_until(unique_lock<mutex>& lock, 
                Predicate pred, 
                interrupt_token itoken);

template <class Rep, class Period, class Predicate>
bool wait_for(unique_lock<mutex>& lock, 
              const chrono::duration<Rep, Period>& rel_time, 
              Predicate pred, 
              interrupt_token itoken);

template <class Clock, class Duration, class Predicate>
bool wait_until(unique_lock<mutex>& lock, 
                const chrono::time_point<Clock, Duration>& abs_time, 
                Predicate pred, 
                interrupt_token itoken);

 

These new overloads require a predicate. The versions ensure to get notified if an interrupt is signalled for the passed std::interrupt_token itoken. After the wait calls, you can check if an interrupt occurred.

cv.wait_until(lock, predicate, itoken);
if (itoken.is_interrupted()){
    // interrupt occurred
}

 

What's next?

As I promised in my last post, my next post is about the remaining rules for defining 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, Animus24, 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, and Rob North.

 

Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

 

My special thanks to Take Up Code TakeUpCode 450 60

 

Seminars

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

Bookable (Online)

German

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++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

Tags: jthread

Comments   

+1 #1 Vladimir 2019-04-20 18:27
Thanks for the summary. As a quick note: I believe that in the table summarizing the methods of itoken (in section "Interrupt Tokens"), the first method listed was supposed to be itoken.valid(), not itoken.value(). No?
Quote
0 #2 Rainer Grimm 2019-06-04 05:36
Quoting Vladimir:
Thanks for the summary. As a quick note: I believe that in the table summarizing the methods of itoken (in section "Interrupt Tokens"), the first method listed was supposed to be itoken.valid(), not itoken.value(). No?

Thanks, I fixed it but in the meantime the names changed once more.
Quote
+4 #3 Mike 2022-08-10 14:19
FYI that std::interrupt_token is now called std::stop_token (this blog post still comes up as the second result when googling "c++ jthread")
Quote

Stay Informed about my Mentoring

 

Mentoring

English 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

Course: The All-in-One Guide to C++20

Course: Master Software Design Patterns and Architecture in C++

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 1489

Yesterday 6503

Week 27746

Month 7992

All 12086201

Currently are 198 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments