std::thread
/ 4 min read
Table of Contents
std::thread in C++11 finally brought standard threading to C++. No more platform-specific pthread or Win32 API - just portable, easy-to-use threading.
What std::thread Actually Does
std::thread wraps a native thread and lets you run functions concurrently:
#include <thread>#include <iostream>
void worker_function() { std::cout << "Running in thread: " << std::this_thread::get_id() << std::endl;}
std::thread t(worker_function);t.join(); // Wait for thread to completeEach std::thread object represents one thread of execution.
Creating Threads
You can pass almost anything to a thread constructor:
// Function pointervoid print_message(const std::string& msg) { std::cout << msg << std::endl;}std::thread t1(print_message, "Hello from thread!");
// Lambdastd::thread t2([]() { std::cout << "Lambda thread!" << std::endl;});
// Member functionclass Worker {public: void do_work(int n) { std::cout << "Working with: " << n << std::endl; }};
Worker w;std::thread t3(&Worker::do_work, &w, 42);
// All threads need to be joined or detachedt1.join();t2.join();t3.join();When I Actually Use std::thread
Most of the time, I use threads for:
- Background tasks: Long-running operations that shouldn’t block the main thread:
class FileProcessor { std::thread background_thread_; std::atomic<bool> should_stop_{false};
public: void start_processing() { background_thread_ = std::thread([this]() { while (!should_stop_) { process_pending_files(); std::this_thread::sleep_for(std::chrono::seconds(1)); } }); }
void stop() { should_stop_ = true; if (background_thread_.joinable()) { background_thread_.join(); } }};- Producer-consumer scenarios: Processing data pipelines:
#include <queue>#include <mutex>#include <condition_variable>
class TaskQueue { std::queue<std::function<void()>> tasks_; std::mutex mutex_; std::condition_variable cv_; bool shutdown_ = false;
public: void add_task(std::function<void()> task) { { std::lock_guard<std::mutex> lock(mutex_); tasks_.push(std::move(task)); } cv_.notify_one(); }
void worker_thread() { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(mutex_); cv_.wait(lock, [this] { return !tasks_.empty() || shutdown_; });
if (shutdown_ && tasks_.empty()) break;
task = std::move(tasks_.front()); tasks_.pop(); } task(); // Execute outside of lock } }};- Parallel processing: Breaking work into chunks:
template<typename Iterator, typename Function>void parallel_for_each(Iterator first, Iterator last, Function func) { const size_t num_threads = std::thread::hardware_concurrency(); const size_t chunk_size = std::distance(first, last) / num_threads;
std::vector<std::thread> threads;
for (size_t i = 0; i < num_threads; ++i) { auto chunk_start = first + i * chunk_size; auto chunk_end = (i == num_threads - 1) ? last : chunk_start + chunk_size;
threads.emplace_back([chunk_start, chunk_end, func]() { std::for_each(chunk_start, chunk_end, func); }); }
for (auto& t : threads) { t.join(); }}
// Usagestd::vector<int> numbers(10000);parallel_for_each(numbers.begin(), numbers.end(), [](int& n) { n = expensive_computation(n);});- Timeouts and periodic tasks:
class PeriodicTimer { std::thread timer_thread_; std::atomic<bool> running_{true};
public: template<typename Function> PeriodicTimer(std::chrono::milliseconds interval, Function func) { timer_thread_ = std::thread([this, interval, func]() { while (running_) { auto start = std::chrono::steady_clock::now(); func();
auto elapsed = std::chrono::steady_clock::now() - start; if (elapsed < interval) { std::this_thread::sleep_for(interval - elapsed); } } }); }
~PeriodicTimer() { running_ = false; if (timer_thread_.joinable()) { timer_thread_.join(); } }};Thread Lifecycle Management
Threads must be either joined or detached:
void demonstrate_lifecycle() { std::thread t([]() { std::cout << "Thread work" << std::endl; });
// Option 1: Wait for completion t.join();
// Option 2: Detach and let it run independently // t.detach(); // Thread becomes daemon-like
// Option 3: Check if joinable first if (t.joinable()) { t.join(); }
// NOT doing any of these = std::terminate when t destructor runs!}Thread-Safe Communication
Use atomic variables or mutexes for thread communication:
class ThreadSafeCounter { std::atomic<int> count_{0};
public: void increment() { ++count_; } int get() const { return count_; }};
class DataSharer { std::vector<int> shared_data_; mutable std::mutex mutex_;
public: void add_data(int value) { std::lock_guard<std::mutex> lock(mutex_); shared_data_.push_back(value); }
std::vector<int> get_copy() const { std::lock_guard<std::mutex> lock(mutex_); return shared_data_; // Return copy }};Real World Example
Here’s a simple thread pool I built:
class ThreadPool { std::vector<std::thread> workers_; std::queue<std::function<void()>> tasks_; std::mutex queue_mutex_; std::condition_variable condition_; bool stop_ = false;
public: ThreadPool(size_t num_threads) { for (size_t i = 0; i < num_threads; ++i) { workers_.emplace_back([this] { while (true) { std::function<void()> task;
{ std::unique_lock<std::mutex> lock(queue_mutex_); condition_.wait(lock, [this] { return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty()) return;
task = std::move(tasks_.front()); tasks_.pop(); }
task(); } }); } }
template<typename F> void enqueue(F&& f) { { std::lock_guard<std::mutex> lock(queue_mutex_); tasks_.emplace(std::forward<F>(f)); } condition_.notify_one(); }
~ThreadPool() { { std::lock_guard<std::mutex> lock(queue_mutex_); stop_ = true; } condition_.notify_all();
for (std::thread& worker : workers_) { worker.join(); } }};
// UsageThreadPool pool(4);pool.enqueue([]() { std::cout << "Task 1" << std::endl; });pool.enqueue([]() { std::cout << "Task 2" << std::endl; });Common Gotchas
- Always join or detach - forgetting this calls
std::terminate - Data races - always protect shared data with synchronization
- Exception safety - exceptions in threads can terminate the program
- Resource cleanup - make sure threads clean up properly
The Pattern I Follow
For most use cases, I prefer higher-level abstractions like std::async or thread pools over raw std::thread:
// Raw thread - more manual workstd::thread t(compute_something, data);t.join();
// std::async - often easierauto future = std::async(std::launch::async, compute_something, data);auto result = future.get();But std::thread is essential when you need precise control over thread lifetime and behavior. It’s the foundation that everything else builds on.