Concurrency
std::thread
Create and manage system threads using std::thread and modern std::jthread (C++20).
Interview: Dangers of joinable thread destruction, parameter passing safety (dangling references), and std::jthread automatic lifetime management.
In C++, multi-threading is supported via the <thread> header. Starting in C++20, std::jthread was introduced to fix lifetime pitfalls in the original std::thread.
Thread Lifetimes
A thread must be either joined (waited on) or detached (run in background) before its handle goes out of scope.
std::jthread
Introduced in C++20. Automatically joins on destruction and supports cooperative stop requests via stop tokens.
Reference Passing
Parameters are copied by default. To pass arguments by reference, wrap them in std::ref to prevent compilation issues.
The Joinable Destruction Trap
If a standard std::thread object is destroyed while it is still joinable (i.e. it has not been joined or detached, even if it has finished execution), the C++ runtime calls std::terminate immediately.
C++20's std::jthread prevents this by calling join() in its destructor automatically.
Code Walkthrough
Demonstrates the use of standard threads and cooperative stop tokens with C++20 jthreads.
#include <iostream> #include <thread> #include <chrono>void workerTask(std::stop_token stopToken, int id) { while (!stopToken.stop_requested()) { std::cout << "Worker " << id << " is executing...\n"; std::this_thread::sleep_for(std::chrono::milliseconds(200)); } std::cout << "Worker " << id << " received stop request and exited.\n"; }
int main() { { // std::jthread automatically joins on scope exit std::jthread jt(workerTask, 1); std::this_thread::sleep_for(std::chrono::milliseconds(500));
// Destructor of jt will request stop and join automatically } std::cout << "jthread scope exited clean.\n"; return 0; }
Interview-Relevant Information
Q: What happens if a std::thread object is destroyed while still joinable?
Answer: The application crashes immediately via std::terminate. This happens because C++ cannot safely decide whether to wait for the thread (blocking) or leave it running detached (risking access to deleted stack frames). Developers must call join() or detach() before destruction, or use std::jthread.
Q: Why do you need std::ref when passing arguments to a thread by reference?
Answer: Thread constructors copy parameters into internal thread-local storage before passing them to the worker function. If the worker accepts a reference (e.g. T&), compilation fails because you cannot bind an lvalue reference to a copied rvalue. Wrapping the argument in std::ref passes a reference wrapper instead, allowing compilation and direct reference sharing.
Quick Checklist
Did you ensure all standard threads are joined or detached before destruction? Did you wrap reference variables in std::ref? If yes, your thread creation is safe.
Use Cases
Offloading complex calculations (e.g., ray tracing, file parsing) to background threads to keep the main UI thread responsive.
Spawning long-running connection listeners in server applications.
Common Mistakes
Forgetting to call join() or detach() on standard std::thread instances, resulting in program termination on scope exit.
Passing references to variables on the local stack frame to a detached thread, resulting in undefined behavior when the stack is cleaned up.