A new thread in C++20 (jthread)
C++20 is coming with a bunch of cool new features, one of which I’ll shortly cover here: the std::jthread
.
The implementation of this std::jthread
is based on the already existing std::thread
. It’s basically a wrapper that brings two new features to the threads: they are cooperatively interruptible and join by default. Before going deeper into these two terms, note that the std::jthread
object wraps anstd::thread
as a member, providing the very same public functions, which simply transfer down the calls. This enables us to change any std::thread
into std::jthread
, knowing for sure that it’ll work as before.
The jthread is cooperatively interruptible
The name suggests that the new jthread
is interruptable, i.e. there is a way to stop a thread from outside. Unlike C++, in some other languages the thread classes have abort(), stop() or interrupt() functions and mostly they are not what users might expect, that is, a kill switch. Some might think it’s so bad that we don’t have such a thing with std::thread
and that it’s so good that now with std::jthread
we finally have it. But it’s cooperatively interruptable, and the best way to understand this is to take a look at the function for it: request_stop()
. The name is chosen very carefully. Consider this example:
Here the main thread creates a new thread, which repeatedly does something (prints a line) every second, forever. The main thread then continues with a 5-second-job, after which waits for the other thread to finish. But it won’t finish, it will keep on running forever, and the main thread will keep on waiting.
We’ve just changed the thread
to jthread
and, as promised, nothing new happens, it behaves as before. Now let’s use that function to stop the thread after main
is done.
This compiles and is valid, but it doesn’t stop the execution of that thread, not instantly, not ever. And that’s ok: note, it says request stop, not insist or force. So we (from outside the thread) can only request a stop and that thread itself has the final say. That’s why it’s cooperatively interruptable.
This finally works. Of course, we could have achieved this with an atomic boolean, and this is a more extensive and trustworthy version of it. But the question still remains: why don’t we have a killswitch on jthread, especially when others (other languages) do? The answer is: others don’t really have it and you don’t want to have it either.
Why a killswitch is a bad idea?
Say you’ve created a worker thread from the main thread and at some point, you want to kill it (the worker). Imagine there is a killswitch, a stop()
function which:
- removes worker from the thread scheduler (PAUSE)
- frees the memory worker used as a stack, without calling the destructors (WIPE OUT)
This almost always guarantees a deadlock or a memory leak or both. So this definitely is a bad idea, some cleanup is necessary anyway. What other languages mostly do is throw an exception from worker, which lays the responsibility of catching and doing the cleanup back on your shoulders. Let’s not forget that std::thread
is just a cover, and mostly it’s a pthread
underneath. It also has somewhat similar and we can always get the native_handle()
from the std::thread
and work with it. But it’s even more complicated.
But the idea is the same: before using that “killswitch” you should take care of the cleanup yourself.
Now, imagine you’re inventing a new language that supports multithreading and has a Thread
class. Would you give it a stop
functionality which isn’t really what the name suggests and contains a high risk of misuse? I would not, so that users are forced to find a way around, the right way.
The jthread joins by default
The second feature jthread
brings is to help us with the dilemma std::thread
used to lead to: to join or to detach. Now jthread
comes to take some responsibility. Its destructor is simply implemented as:
This is exactly what we’ve done in our example above, and so we can remove the last two lines there.
But why the join was chosen as the default end for a jthread? First of all, it’s very safe compared to the detach, and also, in most cases, that’s what you really need.
To get a better understanding of the dangers of detach and why joining should always be your choice check this out: “Let me detach those threads for you”.
Even though it comes with C++20, std::jthread
doesn’t use any of the new language features, so it’s practically available even now. Here is an implementation of it by Nicolai Josuttis, the person who proposed it. Two header files from this repository are all you need to fully access this new feature: “jthread.hpp” and “stop_token.hpp” in “sources/”.