Run work in parallel with std::thread, protect shared data with mutexes and atomics, and coordinate threads using futures and condition variables.
std::thread launches a function on a new thread of execution. You must either join() it (wait for it to finish) or detach() it before the thread object is destroyed, or the program terminates. Threads let you use multiple CPU cores.
#include <iostream>
#include <thread>
void worker(int id) {
std::cout << "thread " << id << " running\n";
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join(); // wait for t1 to finish
t2.join(); // wait for t2
std::cout << "all done\n";
return 0;
}When two threads touch the same data and at least one writes, that’s a data race — undefined behavior. A std::mutex guarded by std::lock_guard (RAII) serializes access to a critical section. For a single counter, a std::atomic is a lock-free alternative.
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>
int main() {
std::atomic<int> counter{0}; // safe without a mutex
std::mutex mtx;
int guarded = 0;
auto job = [&] {
for (int i = 0; i < 1000; ++i) {
++counter; // atomic increment
std::lock_guard<std::mutex> lock(mtx);
++guarded; // protected by the mutex
}
};
std::vector<std::thread> pool;
for (int i = 0; i < 4; ++i) pool.emplace_back(job);
for (auto& t : pool) t.join();
std::cout << counter << " " << guarded << "\n"; // 4000 4000
return 0;
}std::async runs a task and hands back a std::future you call get() on to retrieve the result later — the simplest way to get a value off a thread. For hand-rolled coordination, a std::condition_variable lets one thread wait until another signals a condition is ready.
#include <iostream>
#include <future>
int slowSquare(int x) {
return x * x; // pretend this is expensive
}
int main() {
std::future<int> f = std::async(std::launch::async, slowSquare, 12);
// ... do other work here while it computes ...
std::cout << f.get() << "\n"; // 144 — blocks until the result is ready
return 0;
}