$ ./bin/01_race_tsan.tsan racy
what it printed on the machine that wrote this lab
WARNING: ThreadSanitizer: data race (pid=56417)
Write of size 8 at 0x000001899760 by thread T2:
#0 racy() .../concurrency/01_race_tsan.cpp:12:72
Previous write of size 8 at 0x000001899760 by thread T1:
#0 racy() .../concurrency/01_race_tsan.cpp:12:72
SUMMARY: ThreadSanitizer: data race .../01_race_tsan.cpp:12:72 in racy()
racy expected 4000000 got 2089718 WRONG (183.8 ms, 45.9 ns/increment)
$ ./bin/01_race_tsan.tsan collapsed
what it printed on the machine that wrote this lab
WARNING: ThreadSanitizer: data race (pid=56425)
#0 racy_collapsed() .../01_race_tsan.cpp:11:78
$ ./bin/01_race_tsan.tsan atomic -> 0 warnings
$ ./bin/01_race_tsan.tsan mutex -> 0 warnings
what it printed on the machine that wrote this lab
Read it: TSan reports the two conflicting accesses with their stacks,
INCLUDING for the collapsed version whose output was correct. It does
not need the race to misbehave: it tracks happens-before relations
(via locks and atomics) and flags any pair of unordered conflicting
accesses. Cost: ~5-15x slower, 5-10x memory; run your test suite under
it regularly, not in production.
THE COSTS (this machine, two threads on two P-cores):
atomic relaxed fetch_add 38.7 ns/increment the line ping-pongs between cores (cache lab 04)
std::mutex lock/unlock 107.0 ns/increment two atomics plus, under contention, futex sleeps (lab 04 here)
Both are "correct"; both are slow because they SHARE. The fast fix is
per-thread counters combined at the end: no sharing on the hot path.