Threads
A thread is one path through your code: it runs one statement after another, with its own stack. Every program starts with one thread, the main thread, which runs main().
std::thread a(work), b(work); starts two new threads, and each runs work(). Now three threads run at once. On a CPU with several cores they can truly run in parallel, which is how threads make a program faster.
All threads of a program share its memory. Threads a and b both change the same global variable, hits. Each adds 1 to it 100000 times, so we expect 200000 at the end.
A data race
With a plain int, ++hits is not one step. The CPU loads hits into a register, adds 1 there, and stores the result back. Say hits is 5: thread a loads 5, makes 6, stores 6.
Now both threads run it at the same moment. a loads 5, b loads 5 before a stores. Both make 6, both store 6. Two additions happened, but hits only grew by one: an update is lost.
Two threads use the same variable, at least one writes, and nothing orders them: that is a data race. In C++ it is undefined behaviour. In practice the total often comes out too low and changes from run to run.
Atomics
The fix changes only line 2: hits becomes a std::atomic<int>. Now ++hits is a single indivisible step, a read-modify-write. No other thread can see or change hits half way through. The loop stays the same.
If both threads add at the same moment, one goes first and the other works on its result. Each atomic add reads the latest value. 5 becomes 6, then 7: no update is lost.
Join and print
a.join() makes main wait until thread a has finished, then b.join() waits for b. A std::thread that is still joinable (not yet joined or detached) when it is destroyed makes the program call std::terminate, so join it first.
Only now does main print hits: 200000, on every run. Build it with g++ -std=c++20 -pthread count.cpp -o count. With two cores free, both loops can run side by side.
Going faster
Atomics are not free: the cores can have to pass hits back and forth between them. Faster: each thread counts in its own local variable and adds it to hits once at the end. Same 200000, far less sharing.
What happens
;; step 1/11 · one-thread
thread one path of execution: statement after statement
each thread has its own stack and its own place in the code
the main thread starts at main() (line 7)
for now, the counter is a plain int (line 2):
int hits = 0;
the card leaves out three lines at the top: #include <atomic> #include <iostream> #include <thread>
The card leaves out the program's first three lines, #include <atomic>, #include <iostream>, #include <thread>. The full program compiles with g++ or clang++ using -std=c++20 -Wall -Wextra and prints 200000. -pthread tells GCC and Clang to use the system's POSIX threads library. Recent Linux and macOS systems often build without it, but older Linux systems need it, so it is safest to always add it. Microsoft's compiler needs no extra flag.