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!

Leave a Reply