Creating and Managing Threads
Frequently Asked Questions
QHow many threads should I create?
QWhat happens if a thread throws an exception?
QWhat is thread_local storage and when should I use it?
QWhat are the risks of calling detach() on a thread?
#include <iostream>
#include <thread>
#include <format>
#include <vector>
#include <stop_token>
void simple_task(int id) {
std::cout << std::format("Thread {} starting\n", id);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << std::format("Thread {} finished\n", id);
}
void task_with_result(int id, int& result) {
result = id * id;
}int main() {
std::cout << std::format("Hardware threads: {}\n",
std::thread::hardware_concurrency());1 Basic thread creation
Basic thread creation.
Prefer std::jthread (C++20) which auto-joins.
detach() calls std::terminate.
Use it in code like: std::thread t1(simple_task, 1);.
detach() calls std::terminate. Prefer std::jthread (C++20) which auto-joins.
Watch out: a std::thread that goes out of scope without join() or
std::thread t1(simple_task, 1);
std::thread t2(simple_task, 2);
// Must join or detach before destructor!
t1.join(); // Wait for completion
t2.join();2 Thread with reference parameter
Thread with reference parameter.
Use this when it cleanly solves the problem in front of you.
wrap references in std::ref(); forgetting this causes a compile error or silent copy.
Use it in code like: int result = 0;.
wrap references in std::ref(); forgetting this causes a compile error or silent copy.
Watch out: std::thread copies its arguments by default. You must
int result = 0;
std::thread t3(task_with_result, 5, std::ref(result));
t3.join();
std::cout << std::format("Result: {}\n", result);3 Lambda threads
A lambda defines an unnamed callable object directly at the use site.
Use lambdas for short callbacks, predicates, and local behavior.
They keep behavior close to the call site and avoid extra named functor types.
Write [captures](params) { body } and keep captured state explicit.
running even after main() returns, potentially accessing destroyed objects. Only detach when the thread is truly self-contained.
Watch out: detach() makes the thread independent -- it continues
std::vector<std::thread> workers;
for (int i = 0; i < 4; ++i) {
workers.emplace_back([i] {
std::cout << std::format("Lambda worker {} running\n", i);
});
}
for (auto& w : workers) {
w.join();
}4 std::jthread -- C++20 auto-joining thread with cooperative
std::jthread -- C++20 auto-joining thread with cooperative.
Use this when it cleanly solves the problem in front of you.
cancellation via stop_token thread ignores the stop_token it will still block until the thread finishes naturally.
Use it in code like: std::cout << "\n--- std::jthread with stop_token ---\n";.
cancellation via stop_token thread ignores the stop_token it will still block until the thread finishes naturally.
Watch out: jthread's destructor requests stop AND joins. If your
std::cout << "\n--- std::jthread with stop_token ---\n";
{
std::jthread worker([](std::stop_token stoken) {
int count = 0;
while (!stoken.stop_requested()) {
std::cout << std::format("jthread working... (iteration {})\n", ++count);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
std::cout << "jthread received stop request, exiting cleanly\n";
});
// Let the worker run for a bit
std::this_thread::sleep_for(std::chrono::milliseconds(180));
// jthread destructor calls request_stop() then join() automatically
std::cout << "jthread going out of scope (auto-stop + auto-join)\n";
}
std::cout << "All threads completed\n";
return 0;
}