Cooperative Interruption of a Thread in C++20
This is a cross-post from www.ModernesCpp.com.
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 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 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 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 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, 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.
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.
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, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Wolfgang G?rtner, Louis St-Amour, Stephan Roslen, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Avi Kohn, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, and Tobi Heideman.
Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, and Richard Sargeant.
My special thanks to Embarcadero
Seminars
I'm happy to give online-seminars or face-to-face seminars world-wide. Please call me if you have any questions.
Bookable (Online)
Deutsch
- Embedded Programmierung mit modernem C++: 12.04.2021 - 14.04.2021 (CEST)
- Clean Code mit modernem C++: 22.06.2021 - 24.06.2021 (CEST)
Standard Seminars
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 Patterns and Architecture Patterns with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
New
Contact Me
- Tel.: +49 7472 917441
- Mobil: +49 152 31965939
- Mail: [email protected]
- German Seminar Page: www.ModernesCpp.de
- English Seminar Page: www.ModernesCpp.net
Entrepreneur, Leader, Architect, Full-Stack Extreme Virtuoso: Business Analysis, Cyber Security, Data Science. ITIL BPM SLM Expert bringing Modern Approaches to drive Business Processes.
3 年Thank you for the post Rainer!