Thread Management
Threads allow programs to perform multiple operations concurrently. C++11 introduced std::thread for portable threading.
Basic Thread Creation
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread!\n";
}
int main() {
std::thread t(hello); // Create and start thread
t.join(); // Wait for thread to finish
return 0;
}
Thread Lifecycle
#include <thread>
void task() {
// Do work
}
int main() {
std::thread t(task);
// Must either join or detach before destruction!
if (t.joinable()) {
t.join(); // Wait for completion
// OR
// t.detach(); // Run independently
}
// t.~thread() would call std::terminate if not joined/detached
}
danger
A std::thread object must be either joined or detached before destruction, or std::terminate() is called!
Passing Arguments
#include <thread>
#include <string>
void printMessage(int id, const std::string& msg) {
std::cout << "Thread " << id << ": " << msg << '\n';
}
int main() {
std::thread t1(printMessage, 1, "Hello");
std::thread t2(printMessage, 2, "World");
t1.join();
t2.join();
}
Passing by Reference
#include <thread>
void increment(int& value) {
++value;
}
int main() {
int counter = 0;
// Must use std::ref for references
std::thread t(increment, std::ref(counter));
t.join();
std::cout << counter << '\n'; // 1
}
Passing Member Functions
#include <thread>
class Worker {
public:
void doWork(int param) {
// Work
}
};
int main() {
Worker w;
std::thread t(&Worker::doWork, &w, 42);
t.join();
}
Lambdas with Threads
#include <thread>
#include <vector>
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back([i]() {
std::cout << "Thread " << i << '\n';
});
}
for (auto& t : threads) {
t.join();
}
}
Thread Management
join() - Wait for Completion
std::thread t(task);
t.join(); // Block until thread finishes
// Thread resources are cleaned up
detach() - Independent Execution
std::thread t(task);
t.detach(); // Thread runs independently
// Cannot join later, thread manages its own lifetime
joinable() - Check State
std::thread t(task);
if (t.joinable()) {
t.join(); // Safe to join
}
// After join/detach, no longer joinable
assert(!t.joinable());
Thread IDs
#include <thread>
#include <iostream>
void printThreadId() {
std::cout << "Thread ID: " << std::this_thread::get_id() << '\n';
}
int main() {
std::thread t1(printThreadId);
std::thread t2(printThreadId);
std::cout << "Main thread ID: " << std::this_thread::get_id() << '\n';
std::cout << "t1 ID: " << t1.get_id() << '\n';
std::cout << "t2 ID: " << t2.get_id() << '\n';
t1.join();
t2.join();
}
Thread-Local Storage
#include <thread>
#include <iostream>
thread_local int counter = 0; // Each thread has its own copy
void increment() {
++counter;
std::cout << "Thread " << std::this_thread::get_id()
<< ": counter = " << counter << '\n';
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Main counter: " << counter << '\n'; // 0 (separate copy)
}
Hardware Concurrency
#include <thread>
#include <iostream>
int main() {
unsigned int numThreads = std::thread::hardware_concurrency();
std::cout << "Hardware threads: " << numThreads << '\n';
// Create optimal number of threads
std::vector<std::thread> workers;
for (unsigned int i = 0; i < numThreads; ++i) {
workers.emplace_back(doWork);
}
for (auto& t : workers) {
t.join();
}
}
Practical Examples
Example 1: Parallel Sum
#include <thread>
#include <vector>
#include <numeric>
void partialSum(const std::vector<int>& data, size_t start, size_t end,
long long& result) {
result = std::accumulate(data.begin() + start,
data.begin() + end, 0LL);
}
long long parallelSum(const std::vector<int>& data) {
size_t numThreads = std::thread::hardware_concurrency();
size_t chunkSize = data.size() / numThreads;
std::vector<std::thread> threads;
std::vector<long long> results(numThreads);
for (size_t i = 0; i < numThreads; ++i) {
size_t start = i * chunkSize;
size_t end = (i == numThreads - 1) ? data.size() : (i + 1) * chunkSize;
threads.emplace_back(partialSum, std::cref(data), start, end,
std::ref(results[i]));
}
for (auto& t : threads) {
t.join();
}
return std::accumulate(results.begin(), results.end(), 0LL);
}
Example 2: RAII Thread Wrapper
#include <thread>
class ThreadGuard {
std::thread& thread_;
public:
explicit ThreadGuard(std::thread& t) : thread_(t) {}
~ThreadGuard() {
if (thread_.joinable()) {
thread_.join();
}
}
ThreadGuard(const ThreadGuard&) = delete;
ThreadGuard& operator=(const ThreadGuard&) = delete;
};
void usage() {
std::thread t(task);
ThreadGuard guard(t);
// If exception thrown, guard ensures thread is joined
riskyOperation();
} // guard destructor joins thread
Example 3: Scoped Thread (C++20)
#include <thread>
class ScopedThread {
std::thread thread_;
public:
template<typename... Args>
explicit ScopedThread(Args&&... args)
: thread_(std::forward<Args>(args)...) {}
~ScopedThread() {
if (thread_.joinable()) {
thread_.join();
}
}
ScopedThread(const ScopedThread&) = delete;
ScopedThread& operator=(const ScopedThread&) = delete;
};
void usage() {
ScopedThread t(task); // Automatically joined on scope exit
}
Sleep and Yield
#include <thread>
#include <chrono>
void sleepExample() {
// Sleep for duration
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Sleep until time point
auto wakeup = std::chrono::system_clock::now() + std::chrono::seconds(1);
std::this_thread::sleep_until(wakeup);
// Yield to other threads
std::this_thread::yield();
}
Exception Handling
#include <thread>
#include <exception>
void riskyTask() {
try {
// May throw
throw std::runtime_error("Error!");
}
catch (const std::exception& e) {
// Handle in thread
std::cerr << "Thread exception: " << e.what() << '\n';
}
}
int main() {
std::thread t(riskyTask);
t.join();
// Exception handled in thread, doesn't propagate to main
}
Best Practices
success
DO:
- Always join or detach threads before destruction
- Use RAII wrappers for automatic joining
- Consider hardware_concurrency() for thread count
- Handle exceptions within threads
- Use thread-local storage for per-thread data
danger
DON'T:
- Create too many threads (use thread pools instead)
- Forget to join/detach (causes std::terminate)
- Share data without synchronization
- Assume thread execution order
- Ignore thread lifetime issues
Common Patterns
// Pattern 1: Fire and forget (detach)
std::thread([]{ logToFile("message"); }).detach();
// Pattern 2: Collect results with join
std::vector<std::thread> workers;
for (int i = 0; i < n; ++i) {
workers.emplace_back(processChunk, i);
}
for (auto& t : workers) {
t.join();
}
// Pattern 3: Move threads into containers
std::vector<std::thread> threads;
threads.push_back(std::thread(task)); // Move construction
Related Topics
- Mutexes - Thread synchronization
- Condition Variables - Thread coordination
- Thread Pools - Efficient thread management
- Data Races - Avoiding race conditions