Cooperative Interruption of a Thread in C++20
Cooperative Interruption of a Thread in C++20 – MC++ BLOG
A typical question in my C++ seminars is: Can a  thread be killed? Before C++20, my answer is no. With C++20, you can ask a thread politely for its interruption.


First of all. Why is it not a good idea to kill a thread? The answer is relatively easy. You don’t know which state the thread is in when you kill it. Here are two possible malicious outcomes.

The thread is only half-done with its job. Consequently, you don’t know the state of that job and, hence, the state of your program. You end with undefined behavior, and all bets are open.
The thread may be in a critical section and locks a mutex. Killing a thread while it locks a mutex ends with a high probability of deadlock.

Okay, killing a thread is not a good idea. You may ask a thread friendly if it is willing to stop. This is precisely what cooperative interruption in C++20 means. You ask the thread, and the thread can accept or ignore your wish for the interruption.
Cooperative Interruption
The additional functionality of the cooperative interruption thread in C++20 is based on the std::stop_token, the std::stop_callback, and the std::stop_source data types.
std::stop_token, std::stop_callback, and std::stop_source
A std::stop_token, a std::stop_callback, or a std::stop_source allows a thread to asynchronously request an execution to stop or ask if an execution got a stop signal. The std::stop_token can be passed to an operation and afterward be used to poll the token for a stop request actively or to register a callback via std::stop_callback. The stop request is sent by a std::stop_source. This signal affects all associated std::stop_token. The three classes std::stop_source, std::stop_token, and std::stop_callback share the ownership of an associated stop state. The calls request_stop(), stop_requested(), and stop_possible() are atomic.
You can construct a std::stop_source in two ways:


stop_source();                                      // (1)
explicit stop_source(std::nostopstate_t) noexcept;  // (2)


The default constructor (1) constructs a std::stop_source with a new stop state. The constructor taking std::nostopstate_t (2) constructs an empty std::stop_source without associated stop state.
The component std::stop_source src provides the following member functions for handling stop requests.


src.stop_possible() means that src has an associated stop state. src.stop_requested() returns true when src has an associated stop state and was not asked to stop earlier. src.request_stop() is successful and returns true if src has an associated stop state, which was not requested to stop before.
The call src.get_token() returns the stop token stoken. Thanks to stoken you can check if a stop request has been made or can be made for its associated stop source src. The stop token stoken observes the stop source src.
The following table presents the member functions of a std::stop_token stoken.

A default-constructed token that has no associated stop state. stoken.stop_possible also returns true if stoken has an associated stop state. stoken.stop_requested() returns true when the stop token has an associated stop state and has already received a stop request.
If the std::stop_token should be temporarily disabled, you can replace it with a default constructed token. A default constructed token has no associated stop-state. The following code snippet shows how to disable and enable a thread’s capability to accept stop requests.


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


std::stop_token interruptDisabled has no associated stop state. This means the thread jthr can, in all lines except lines (1) and (2), accept stop requests.
When you study the code snippet carefully, you may wonder if the used std::jthread. std::jthread in C++20 is an extended std::thread in C++11. The j in jthread stands for joinable because it joins automatically in its destructor. Its first name was ithread. You may guess why: i stands for interruptible. I will present std::jthread in my next post.
My next example shows the use of callbacks using a std::jthread.


// invokeCallback.cpp

#include <chrono>
#include <iostream>
#include <thread>
#include <vector>

using namespace::std::literals;

auto func = [](std::stop_token stoken) {                             // (1)
        int counter{0};
        auto thread_id = std::this_thread::get_id();
        std::stop_callback callBack(stoken, [&counter, thread_id] {  // (2)
            std::cout << "Thread id: " << thread_id 
                      << "; counter: " << counter << '\n';
        });
        while (counter < 10) {
            std::this_thread::sleep_for(0.2s);
            ++counter;
        }
    };

int main() {
    
    std::cout << '\n';
    
    std::vector<std::jthread> vecThreads(10);
    for(auto& thr: vecThreads) thr = std::jthread(func);
    
    std::this_thread::sleep_for(1s);                              // (3)
    
    for(auto& thr: vecThreads) thr.request_stop();                // (4)

    std::cout << '\n';
    
}


Each of the ten threads invokes the lambda function func (1). The callback (2) displays the thread id and the counter. Due to the one-second sleeping of the main thread (3) and the sleeping of the child threads, the counter is 4 when the callbacks are invoked. The call thr.request_stop() triggers the callback on each thread.

What’s next?
As mentioned,  std::thread has one significant weakness. When you forget to join it, its destructor calls std::terminate, and your program crashes. std::jthread (C++20) overcomes this counter-intuitive weakness and is also interruptible.
 Post Views: 8,645
			
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.



Modernes C++ GmbH
Modernes C++ Mentoring (English)

"Fundamentals for C++ Professionals": open
"Design Patterns and Architectural Patterns in C++": open
"C++20: Get the Details": open
"Concurrency with Modern C++": open
"Generic Programming (Templates) with C++": October 2024
"Embedded Programming with Modern C++": October 2024
"Clean Code: Best Practices for Modern C++": March 2025


Do you want to stay informed about my mentoring programs? Subscribe Here
Rainer Grimm
Yalovastraße 20
72108 Rottenburg

Mobil: +49 176 5506 5086
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.orgModernes C++ Mentoring,








