std::thread (C++11) launches a function on a new OS thread; the thread object MUST have either join() (wait for it to finish) or detach() (let it run independently) called before the object is destroyed, or the program calls std::terminate immediately — a genuine, easy-to-hit footgun. std::mutex and std::lock_guard protect shared data from simultaneous access, the lock_guard releasing automatically via RAII when it goes out of scope. std::jthread, added in C++20, fixes the join footgun structurally: it automatically joins in its own destructor, finally bringing thread management in line with C++'s usual RAII philosophy.
std::thread: the C++11 basics, and its real footgun
Forgetting join() here isn't just a missed cleanup step — it's an immediate std::terminate call and program crash, which is exactly why this pattern needs care every single time.
void printMessage() {
std::cout << "Hello from a thread\n";
}
std::thread t(printMessage);
t.join(); // REQUIRED — skip this and the program calls std::terminate when t is destroyedstd::mutex and std::lock_guard: protecting shared data
lock_guard acquires the mutex in its constructor and releases it in its destructor — the same RAII pattern as smart pointers, meaning the lock is released correctly even if an exception is thrown inside the protected block.
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; i++) {
std::lock_guard<std::mutex> lock(mtx); // acquired here
counter++; // protected
} // automatically released here, even on an exception
}std::jthread (C++20): auto-joining, RAII-style
jt requires no explicit join() at all — its destructor calls join() automatically, finally bringing thread ownership in line with how every other RAII resource in modern C++ behaves.
void doWork() {
std::cout << "Working\n";
}
{
std::jthread jt(doWork); // C++20 — no explicit join() needed
} // automatically joined here, when jt is destroyed — no footgun