Cooperative Interruption of a Thread in C++20

Contents[Show]

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.

 TimelineCpp20Interruption

First of all. Why is it no good idea to kill a thread? The answer is quite easy. You don't know in which state the thread is 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 behaviour, 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 in a deadlock.

Okay, killing a thread is not a good idea. Maybe, you can ask a thread friendly if it is willing to stop. This is exactly 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 afterwards 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.

stopSource

 

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, and it 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.

stopToken

 

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 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 line (1) and (2) accept stop requests.

When you study the code snippet carefully, you may wonder about the used std::jthread. std::jthread in C++20 is an extend 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 interruptable. I 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.

invokeCallback

What's next?

As mentioned,  std::thread from C++11 has one big weakness. When you forget to join it, its destructor calls std::terminate, and your program crashed. std::jthread (C++20) overcomes this counter-intuitive weakness and is also interruptable.

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Tornike Porchxidze, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Dimitrov Tsvetomir, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, and Yacob Cohen-Arazi.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, and Said Mert Turkal.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

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.

New

Contact Me

Modernes C++,

RainerGrimmSmall

Comments   

0 #1 Johannes Sixt 2021-08-20 09:57
The counter variable should be atomic because the callback is invoked from the main thread; otherwise, you have a data race.

I think that callbacks are meant to do only very simple things that are guaranteed not to throw any exceptions, because if they do, terminate() is called. Therefore, I suggest to have a much, much simpler callback in the example.
Quote

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)

Course: C++ Fundamentals for Professionals

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

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 4332

Yesterday 8323

Week 37212

Month 110622

All 7172912

Currently are 178 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments