98 Advanced The Hundred
The Flag the Thread Never Saw
tested at -O0, shipped at -O2
using namespace std::chrono;
bool done{false};
void worker() {
constexpr long long kGiveUp = 6'000'000'000LL;
auto t0 = steady_clock::now();
long long spins = 0;
while (spins < kGiveUp && !done) // spin, but never forever
++spins;
std::cout << "worker: " << (spins < kGiveUp ? "saw the flag" : "gave up") << " after "
<< duration_cast<milliseconds>(steady_clock::now() - t0).count() << " ms\n";
}
int main() {
std::thread w(worker);
std::this_thread::sleep_for(milliseconds(100));
std::cout << "main: setting done = true\n";
done = true;
w.join();
}
Build it at -O2 and run. How long before the worker notices?
Answer
Never. It burns all six billion spins and quits, while the same source built at -O0 sees
the flag in 100 ms. Typical output (GCC 11.5 on x86-64 Linux; the ms figure wobbles per run):
Why¶
Two threads touch done, one writes, and nothing orders them — a data race, and therefore
undefined behavior. The optimizer may then assume no other thread writes done: inside a
loop that never writes it the value cannot change, so the read is lifted out of the loop.
GCC 11.5 does exactly that from -O1 up, checking the flag once and then spinning on a loop
that never touches memory again — at -O2 a single cmp BYTE PTR done[rip], 0 guarding a bare
sub rdx, 1 / jne countdown, and at -O3 not even the countdown survives (0 ms, printed
before the flag is ever set). At -O0 every read really goes to memory, so the bug passes your
debug build and detonates in release. -Wall -Wextra stay silent — a data race is a runtime
property, so catching it is -fsanitize=thread's job, not a warning flag's.
The fix¶
std::atomic<bool> done{false}; // "saw the flag after 100 ms", -O0 through -O3
// volatile only forces the reload: no atomicity, no ordering — not a threading tool in C++
Takeaway: without atomics, the compiler assumes no other thread is touching your variable.
Try it: g++ -std=c++17 -O2 -pthread main.cpp -o demo && ./demo, then -O0. Then change
done to std::atomic<bool> and watch every level agree.