std::jthread
C++20 brought in some nice features, unfortunately some of them are quite unstable and incomplete (looking at you “modules”), nevertheless some of them are quite complete and helpful.
Today we will take a look at std::jthread, also know as “joining thread”. This new threading abstraction is almost similar with its older brother std::thread, but unlike it it provides some neat features such as: automatic join on destruction and a mechanism that allows one to request a stop.
There nothing new when you start a std::jthread:
int main() {
auto jthread = std::jthread([]{
// You don't fear race conditions, don't you?!
std::cout << "hello jthread" << std::endl;
});
}
Just pass any Callable to the constructor and you are good to go.
And the best part? We didn’t even have to check if our thread is joinable and if so then do a join, everything is taken care of by the destructor. If your program is making heavy use of threads then this will already reduce some of the boilerplate code that you have to write.
As for the stop mechanism the standard library employs two new primitives called stop_source and stop_token. We usually don’t have to instantiate these two primitives directly when working with std::jthread because everything is already wrapped into jthread type.
Now in order for a thread to access its own stop_token the function that is passed as the entry point for the thread needs to have as its first parameter a std::stop_token and when some thread wants to signal another thread that it needs to stop it’ll do it via std::jthread::request_stop() member function:
int main() {
auto jthread = std::jthread([](std::stop_token token){
int count = 0;
do {
std::cout << "Hello: " << count++ << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} while(!token.stop_requested());
});
std::this_thread::sleep_for(std::chrono::seconds(2));
jthread.request_stop();
}
The implementation takes care of the thread-safety when manipulating the shared state which reduces the need for explicit mutexes or atomics. If you forget to specify that the first argument of your entry point is a std::stop_token then nothing will happen if you request a stop from another thread. Also once a stop has been issued it can not be reverted.
Overall I think it’s a good feature, not having to explicitly join threads and also not having to explicitly carry extra state only for mere stop functionality it’s great. One may argue that you can deploy your own wrapper over the already existing std::thread, but once something it’s in the standard you have a “set in stone” interface and that’s especially good if you work across multiple projects each with their own wrappers and abstractions.