C++ 20 is here! What is new for multithreaded programming?
C++ 20 will bring the most significant change to modern C++ since C++ 11. Many exciting new features like modules and concept are going to change C++ fundamentally. It also introduces several interesting new features for multithreaded programming and we briefly introduce them in this blog!
std::jthread
and stop tokens
From the start (pre-C++11), many (including me) had wanted threads to have what is now jthread’s behavior.
BJARNE STROUSTRUP, Thriving in a Crowded and Changing World: C++ 2006–2020
The jthread
(short for “joining thread”) is a thread that obeys RAII; that is, its destructor joins, rather than terminates, if the jthread goes out of scope.
inline jthread::~jthread() { if (joinable()) { // if not joined/detached, signal stop and wait for end: request_stop(); join(); } }
While std::thread
requires an explicit call to std::thread::join
to join threads, std::jthread
joins threads implicitly upon destruction, so jthread
can make your program much simpler in some cases. Considered the following example (from the original jthread proposal) which is common in many scenarios:
void compareWithStdThreadJoining() { std::thread t([] { //... }); try { //... } catch (...) { j.join(); throw; // rethrow } t.join(); }
To make sure the thread is joined under all conditions, a call to join()
has to be inserted everywhere in the code. Now using jthread
, the code can be greatly simplied as jthread
is automatically joined when it goes out of the scope.
std::jthread
is cooperative interruptible
As “cooperative interruptable” might sounds complicated, it is actually an old and simple (yet efficient) solution that has already be widely adopted. The basic idea is, if I want a jthread to stop, I set its stop token. It is called “cooperative”, as after the stop token is set, it is the jthread's
obligation to occasionally test the token, and if the token has been set, cleans up and exits. For example, the following code snippet won’t work as what one might expect, which means jthread
won’t stop after calling request_stop()
.
void interruptJThread() { std::jthread([]() { while (true) { cout << "running..." << endl; } }); jthread.request_stop(); }
To make it work, you need to use std::stop_tokens
introduced in C++ 20 similar to the following:
void interruptJThread() { std::jthread([](std::stop_token token) { while (token.stop_requested()) { cout << "running..." << endl; } }); jthread.request_stop(); }
At high level, it is exactly the same as if you use an automic boolean flag to stop the thread. Although it might not be as efficient and clean as the standard API. Also, more advanced uses such as registering a callback upon the specific stop_token
being set is also possible by using std::stop_callback
.
atomic<shared_ptr<T>>
As it might be surprising to you, unlike boost::shared_ptr<T>
, std::shared_ptr<T>
is not thread safe before C++ 20! That is, the increasing and decreasing on the reference count is not atomic thus might cause data races!
Now C++ 20 finally offers a specialization for atomic<shared_ptr<T>>
. In C++20, by using atomic<shared_ptr<T>>
, std::shared_ptr<T>
can finally be maniputated safely by multiple threads!
Conclusion
There are many other new features related to concurrecy that we do not cover in this blog (such as std::atomic_ref<T>
). Besides, although C++20 has made significant improvement in many aspects, the hoped-for general concurrency model (“executors“) still wasn’t ready for C++20 despite valiant efforts and an emerging broad consensus. While there is still long way to improve the way how people do concurrent programming, we hope that coderrect scanner is able to make your life easier!