Use this file to discover all available pages before exploring further.
The C++ Standard Library provides a portable, high-level concurrency model built on four main headers: <thread> for launching and managing threads, <mutex> for mutual exclusion, <condition_variable> for thread synchronization, and <future> for asynchronous task results. The <atomic> header complements these with lock-free, hardware-backed atomic operations. Together they cover the full range from low-level thread management to high-level async task dispatch.
In MSVC, code compiled with /clr (C++/CLI) cannot use <thread>, <mutex>, or <future> — these headers are blocked in that mode. Use the Windows thread pool APIs or the Concurrency Runtime (ConcRT) for managed/native interop scenarios.
std::thread represents a single OS thread of execution. You pass a callable (function, lambda, functor, or pointer-to-member) plus any arguments to the constructor, and the thread starts immediately.
#include <thread>#include <iostream>#include <vector>void worker(int id, int iterations){ for (int i = 0; i < iterations; ++i) std::cout << "Thread " << id << " iteration " << i << "\n";}int main(){ // Launch two threads std::thread t1(worker, 1, 3); std::thread t2(worker, 2, 3); // Must join or detach before thread object is destroyed t1.join(); // wait for t1 to finish t2.join(); // wait for t2 to finish // Lambda thread std::thread t3([] { std::cout << "Lambda thread id: " << std::this_thread::get_id() << "\n"; }); t3.join(); // Pass arguments by reference using std::ref int counter = 0; std::thread t4([&counter] { counter = 42; }); t4.join(); std::cout << "counter = " << counter << "\n"; // 42}
If a std::thread object is destroyed while it is still joinable (neither joined nor detached), the program calls std::terminate. Always call join() or detach() before the thread object goes out of scope.
std::jthread extends std::thread with two important additions: it automatically joins in its destructor (eliminating the risk of std::terminate), and it supports cooperative cancellation via std::stop_token.
#include <thread>#include <iostream>#include <chrono>int main(){ // jthread joins automatically when it goes out of scope std::jthread t([](std::stop_token stoken) { while (!stoken.stop_requested()) { std::cout << "Working...\n"; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } std::cout << "Stopped gracefully.\n"; }); std::this_thread::sleep_for(std::chrono::milliseconds(350)); t.request_stop(); // signal the thread to stop // destructor joins automatically — no explicit join() needed}
#include <thread>#include <iostream>#include <chrono>int main(){ // Get current thread id auto id = std::this_thread::get_id(); std::cout << "Main thread id: " << id << "\n"; // Hint to the scheduler to yield std::this_thread::yield(); // Sleep for a duration std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Sleep until a point in time auto wake = std::chrono::steady_clock::now() + std::chrono::seconds(1); std::this_thread::sleep_until(wake); // Hardware concurrency hint unsigned hw = std::thread::hardware_concurrency(); std::cout << "Logical cores: " << hw << "\n";}
std::condition_variable allows threads to wait until a condition becomes true, avoiding busy-waiting.
#include <condition_variable>#include <mutex>#include <thread>#include <queue>#include <iostream>#include <chrono>// Producer-Consumer pattern using condition_variablestd::queue<int> task_queue;std::mutex queue_mutex;std::condition_variable cv;bool done = false;void producer(int n){ for (int i = 0; i < n; ++i) { { std::lock_guard<std::mutex> lock(queue_mutex); task_queue.push(i); std::cout << "Produced: " << i << "\n"; } cv.notify_one(); // wake one waiting consumer std::this_thread::sleep_for(std::chrono::milliseconds(50)); } { std::lock_guard<std::mutex> lock(queue_mutex); done = true; } cv.notify_all(); // wake all consumers so they can exit}void consumer(int id){ while (true) { std::unique_lock<std::mutex> lock(queue_mutex); // Wait until there is work or we are done cv.wait(lock, [] { return !task_queue.empty() || done; }); while (!task_queue.empty()) { int task = task_queue.front(); task_queue.pop(); lock.unlock(); std::cout << "Consumer " << id << " processed: " << task << "\n"; lock.lock(); } if (done && task_queue.empty()) return; }}int main(){ std::thread prod(producer, 8); std::thread cons1(consumer, 1); std::thread cons2(consumer, 2); prod.join(); cons1.join(); cons2.join();}
Always use cv.wait(lock, predicate) rather than the predicate-less cv.wait(lock). The predicate form handles spurious wakeups — the OS may wake a thread even when notify_one was not called — by re-checking the condition after each wakeup.
A std::shared_future can be copied and shared across multiple threads — each thread can call get() independently:
#include <future>#include <thread>#include <iostream>#include <vector>int main(){ std::promise<int> prom; std::shared_future<int> sf = prom.get_future().share(); std::vector<std::thread> threads; for (int i = 0; i < 5; ++i) { threads.emplace_back([sf, i] { int val = sf.get(); // all five threads block here std::cout << "Thread " << i << " got: " << val << "\n"; }); } prom.set_value(42); // unblocks all five threads at once for (auto& t : threads) t.join();}
std::atomic<T> wraps a value and guarantees that load, store, and read-modify-write operations are indivisible — no mutex needed for simple shared counters and flags.
#include <atomic>#include <thread>#include <vector>#include <iostream>std::atomic<int> counter{0};std::atomic<bool> ready{false};void worker(){ // Spin-wait until ready while (!ready.load(std::memory_order_acquire)) std::this_thread::yield(); // Atomically increment counter.fetch_add(1, std::memory_order_relaxed);}int main(){ std::vector<std::thread> threads; for (int i = 0; i < 100; ++i) threads.emplace_back(worker); ready.store(true, std::memory_order_release); // release all workers for (auto& t : threads) t.join(); std::cout << "counter = " << counter.load() << "\n"; // 100}