Threads, mutexes and data races in C++
Start threads with std::thread, protect shared data with std::mutex and lock_guard, and understand data races, atomics and deadlock.
A thread is an independent path of execution inside your program. All threads share the program's memory, which is what makes them fast to coordinate and also what makes them dangerous.
std::thread t(f);starts runningfright away.t.join()waits for it to finish. Everystd::threadmust be joined (or detached) before it's destroyed, or the program terminates. C++20'sstd::jthreadjoins automatically.- A data race is two threads touching the same memory at the same time, at least one writing, with nothing to synchronize them. It's undefined behavior, and even
counter++races, because it's a separate load, add and store. - A
std::mutexlets one thread at a time into a critical section. Lock it withstd::lock_guard, which unlocks automatically at the end of the scope. std::atomic<int>makes single operations like++andfetch_addindivisible, with no mutex needed.- Two threads locking two mutexes in opposite orders can deadlock. Lock several at once with
std::scoped_lock, or always lock in one agreed order.
The browser compiler on this site has no threads, so this example was compiled with g++ -std=c++20 -pthread and run on Linux. Build with -fsanitize=thread while developing: ThreadSanitizer reports data races and lock-order problems as they happen.
Example
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
int main() {
long long total = 0;
std::mutex m;
std::vector<std::thread> workers;
for (int t = 0; t < 4; t++) {
workers.emplace_back([&total, &m, t] {
long long mine = 0; // work on private data...
for (int i = 1; i <= 1000; i++) mine += i * (t + 1);
std::lock_guard<std::mutex> lock(m); // ...then lock once to combine
total += mine;
});
}
for (auto& w : workers) w.join();
std::cout << "total " << total << "\n";
return 0;
}
Output (compiled with GCC and run on Linux: the in-browser compiler has no threads):
total 5005000