std::jthread (C++20)
Explore the improved thread class with automatic joining and cooperative cancellation.
What is std::jthread?
std::jthread (introduced in C++20) is a safer alternative to std::thread that automatically joins in its destructor and supports cooperative cancellation via stop tokens. It eliminates the risk of crashing from a forgotten join() or detach() call.
As we have seen, std::thread has a footgun: if you forget to call join() or detach(), your program crashes with std::terminate(). This is especially problematic with exceptions:
void dangerous() {
std::thread t(work);
might_throw(); // If this throws...
t.join(); // ...we never reach here
} // CRASH: t destroyed while joinable
We showed how to write RAII wrappers to handle this, but C++20 gives us a better solution built into the standard library: std::jthread.
Introducing std::jthread
std::jthread ("joining thread") is a std::thread that automatically joins in its destructor:
#include <iostream>
#include <thread>
void work() {
std::cout << "Working...\n";
}
int main() {
std::jthread t(work);
// No need to call join()!
} // t automatically joined here
If the std::jthread is still joinable when it is destroyed, it calls join() automatically. No more crashes from forgotten joins.
Exception Safety Built In
void now_safe() {
std::jthread t(work);
might_throw(); // Even if this throws...
// ...t's destructor will join
} // Exception propagates, but t joins cleanly first
Cooperative Cancellation with stop_token
std::jthread has another major feature: cooperative cancellation via std::stop_token. This provides a standardized way to ask a thread to stop.
The Old Way (Manual Flags)
Without stop_token, you would use an atomic flag:
#include <atomic>
std::atomic<bool> stop_requested{false};
void worker_old() {
while (!stop_requested) {
// Do work...
}
std::cout << "Stopped!\n";
}
int main() {
std::thread t(worker_old);
std::this_thread::sleep_for(std::chrono::seconds(1));
stop_requested = true; // Signal thread to stop
t.join();
}
This works, but you need to manage the flag yourself, and every worker function needs access to the right flag.
The New Way (stop_token)
With std::jthread, cancellation is built in:
#include <iostream>
#include <thread>
#include <chrono>
void worker(std::stop_token stoken) {
while (!stoken.stop_requested()) {
std::cout << "Working...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
std::cout << "Stop requested, cleaning up...\n";
}
int main() {
std::jthread t(worker); // stop_token passed automatically!
std::this_thread::sleep_for(std::chrono::seconds(1));
t.request_stop(); // Ask thread to stop
// t.join() called automatically in destructor
}
Output:
Working...
Working...
Working...
Working...
Working...
Stop requested, cleaning up...
How stop_token Works
When you create a std::jthread, it creates an associated std::stop_source. If your thread function's first parameter is std::stop_token, the jthread automatically passes in a token connected to its stop source.
// The stop_token parameter is detected and filled automatically
void worker(std::stop_token stoken) {
while (!stoken.stop_requested()) {
// ...
}
}
// Also works with additional parameters
void worker_with_args(std::stop_token stoken, int id, const std::string& name) {
while (!stoken.stop_requested()) {
std::cout << name << " " << id << " working\n";
}
}
int main() {
std::jthread t(worker_with_args, 42, "Alice");
// stop_token inserted automatically before 42 and "Alice"
}
Automatic Stop on Destruction
Here is the key feature: when a std::jthread is destroyed, it automatically requests a stop before joining:
void worker(std::stop_token stoken) {
while (!stoken.stop_requested()) {
std::cout << "Working...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "Stopping gracefully\n";
}
int main() {
{
std::jthread t(worker);
std::this_thread::sleep_for(std::chrono::milliseconds(350));
// No explicit request_stop() call
} // Destructor calls request_stop(), then join()
std::cout << "Thread finished\n";
}
Output:
Working...
Working...
Working...
Stopping gracefully
Thread finished
This means well-designed worker functions automatically clean up when their jthread goes out of scope.
The stop_token Family
C++20 provides three related types:
| Type | Purpose |
|---|---|
std::stop_token |
Check if stop was requested (read-only) |
std::stop_source |
Request a stop (write) |
std::stop_callback |
Execute callback when stop is requested |
stop_source
You can get the stop_source from a jthread to share stop control:
std::jthread t(worker);
std::stop_source& source = t.get_stop_source();
// Anyone with the source can request a stop
source.request_stop();
// Check if a stop has been requested
if (source.stop_requested()) {
std::cout << "Stop was requested\n";
}
stop_callback
You can register callbacks to run when a stop is requested:
void worker(std::stop_token stoken) {
std::stop_callback callback(stoken, []() {
std::cout << "Cleanup callback executed!\n";
});
while (!stoken.stop_requested()) {
// Do work...
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
#include <condition_variable>
std::mutex mtx;
std::condition_variable_any cv;
void waiter(std::stop_token stoken) {
std::unique_lock lock(mtx);
// Wait until stop is requested
// condition_variable_any works with stop_token!
cv.wait(lock, stoken, []() { return false; });
std::cout << "Woken up by stop request!\n";
}
Comparing std::thread and std::jthread
| Feature | std::thread | std::jthread |
|---|---|---|
| Auto-joins on destruction | No (terminates) | Yes |
| Built-in cancellation | No | Yes (stop_token) |
| Auto-requests stop on destruction | No | Yes |
| Available since | C++11 | C++20 |
When to Use Which
Use std::jthread when:
- You are on C++20 or later (preferred default)
- You want automatic joining
- You need cooperative cancellation
- Exception safety is important
Use std::thread when:
- You are stuck on C++11/14/17
- You specifically need
detach()behavior - You are integrating with existing code that uses
std::thread
A Complete Example
Here is a more realistic example showing a background worker that can be cleanly stopped:
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <optional>
class BackgroundProcessor {
std::queue<int> work_queue_;
std::mutex mutex_;
std::jthread worker_;
void process_work(std::stop_token stoken) {
while (!stoken.stop_requested()) {
std::optional<int> item;
{
std::lock_guard lock(mutex_);
if (!work_queue_.empty()) {
item = work_queue_.front();
work_queue_.pop();
}
}
if (item) {
std::cout << "Processing: " << *item << '\n';
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
std::cout << "Worker shutting down\n";
}
public:
BackgroundProcessor()
: worker_(&BackgroundProcessor::process_work, this) {}
// Destructor automatically stops and joins the worker!
void add_work(int item) {
std::lock_guard lock(mutex_);
work_queue_.push(item);
}
};
int main() {
{
BackgroundProcessor processor;
for (int i = 1; i <= 10; ++i) {
processor.add_work(i);
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "Destroying processor...\n";
} // processor's destructor stops and joins worker
std::cout << "Done!\n";
}
Output:
Processing: 1
Processing: 2
Processing: 3
Processing: 4
Processing: 5
Destroying processor...
Worker shutting down
Done!
The worker thread cleanly shuts down when the BackgroundProcessor is destroyed, with no explicit shutdown code needed in main().
Summary
std::jthreadautomatically joins in its destructor - no more forgotten joinsstd::stop_tokenprovides cooperative cancellation built into the threadrequest_stop()asks a thread to stop;stop_requested()checks the requeststd::jthreaddestructor callsrequest_stop()thenjoin()automaticallystd::stop_callbacklets you run code when a stop is requested- Prefer
std::jthreadoverstd::threadin C++20 and later
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
std::jthread (C++20) - Quiz
Test your understanding of the lesson.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!