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.
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.
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
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.
std::stop_callback
std::stop_token
request_stop()
stop_requested()
stop_possible()
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.
std::nostopstate_t
std::stop_source
std::stop_source src
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.
src.stop_possible()
src
src.stop_requested()
true
src
src.request_stop()
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.
src.get_token()
stoken
stoken
The following table presents the member functions of a std::stop_token stoken.
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.
stoken.stop_possible
stoken.stop_requested()
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.
std::stop_token interruptDisabled
jthr
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.
std::jthread. std::jthread
std::thread
jthread
ithread
std::jthread
My next example shows the use of callbacks using a std::jthread.
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.
func
id
counter
thr.request_stop()
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. 121 guests and no members online
Kubik-Rubik Joomla! Extensions
Read more...
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.