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 an
std::thread as a member, providing the very same public functions, which simply transfer down the calls. This enables us to change any
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
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/”.