thecodex.expert · The Codex Family of Knowledge
C++

Concurrency

Forgetting to call join() before a std::thread object is destroyed doesn’t just leak the thread — it calls std::terminate and crashes the entire program immediately.

C++20 / C++23 jthread auto-joins (C++20) Last verified:
Canonical Definition

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.

C++basic_thread.cpp
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 destroyed

std::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.

C++mutex_example.cpp
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.

C++jthread_example.cpp
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

Sources

1
ISO/IEC 14882 (C++ Standard), "Thread support library," cppreference.com/w/cpp/thread.