Foundations — Thread Sanitizer (TSan)
This page builds every symbol the parent note leans on, starting from "what is a thread" and ending at the $V_t[u] < k$ race check. Nothing is assumed. If you already know a section, the reveal lines at the bottom let you skip-test.
1. Thread — the actor that does the touching
Picture two fingers on the same book. Each finger reads its own line, but sometimes both fingers reach for the same word on the same page. That shared word is shared memory (next section).

Why the topic needs it: a race requires two actors. One finger alone can never race with itself — it does one thing at a time. The whole subject only exists because more than one thread runs concurrently.
2. Memory & shared location — the thing being touched
Picture the row of boxes as mailboxes on a street. Box number x is "shared" if thread A and thread B both have a key to it.
Why the topic needs it: the parent's definition says "two threads touching the same memory." That "same box" is the location you'll see in the race check. If two threads only touch different boxes, there is never a race. See Memory Models for how boxes and values behave across cores.
3. Access, Read vs Write — the two kinds of touch
Throughout this page we name two particular accesses with the letters and : think of them as two labelled dots, "the touch called " and "the touch called ", each one performed by some thread on some box.
Distinguish this from a higher-level bug: see Data Race vs Race Condition — a data race is the low-level memory-level fault, not the same thing as a logic-level race condition.
4. Event and its index — the numbered heartbeats of a thread
Picture each thread as a ticking stopwatch that only advances when the thread does something worth recording. The reading on 's stopwatch at the moment of an event is that event's index.
Which events tick the clock? To keep the machinery simple and correct, the parent's rules advance a thread's own counter on synchronization events (lock, unlock, create, join) — not on every ordinary read or write. Plain accesses are merely stamped with the current counter value; they do not increment it. That is why counts sync events, not memory touches.
Why the topic needs it: "the latest event index of that can see" (section 7) only makes sense once "event" and "index" are defined. The stamp in section 8 is an event index.
5. Synchronization — the "your turn" note
Two more synchronization pairs matter for a complete picture:
- Thread create → child's first event: the moment you spawn a thread, everything the parent did before the spawn is visible to the child (an arrow from parent to child).
- Child's last event → join: when you
pthread_joina thread, everything that thread did is visible to you afterwards (an arrow from child to joiner). This is why "write, then join, then read" is safe. - Atomic release → atomic acquire: a store to a std::atomic with release ordering, paired with a load that acquires it, is also a synchronization pair — the same release/acquire handshake as a mutex, without a lock.
Picture a talking stick: only the finger holding the stick may touch the shared box. Handing the stick over (unlock → lock, or create, or join, or atomic release → acquire) is the moment one thread's work becomes visible and ordered before the other's.
Why the topic needs it: synchronization is the only thing that creates order between threads. Without it, two accesses float free, unordered — the exact condition for a race. These pairs are what build the happens-before edge (section 7).
6. Concurrent, AND, NOT — "could go either way"

Why the topic needs it: the whole point of TSan is to catch concurrent-plus-write pairs without the bad interleaving actually happening. "Concurrent" is precisely "not ordered by happens-before."
7. Happens-before () — the arrow of guaranteed order
You already have all three ways an arrow appears:
- Program order — same thread, earlier event index: your one finger does event 1 before event 2, so event-1 event-2 (sections 1, 4).
- Synchronization — an unlock the matching lock, create child, child join, or atomic release acquire (section 5).
- Transitivity — if and , then . Arrows chain.
Why the topic needs it: is the entire theory. TSan does not watch time; it watches these arrows. No arrow between two accesses (one a write) = whistle.
8. Vector clock and the entry — remembering the arrows
A plain counter per thread can say "I have done 5 sync events," but not "how much of thread B's history I am guaranteed to have seen." We need that second thing for the race check. So we upgrade to an array.

- = how many sync events my own thread has done (my personal clock).
- for = how far into thread 's past I am guaranteed to see.
Why the topic needs it: the race check reads exactly one number, , and compares it. Everything above exists to give that number a meaning: "the deepest point in 's history that is ordered after."
9. The stamp and the comparison — reading the whistle
First, the new symbol. When thread last touched box , TSan recorded when — specifically 's own event index at that instant. Call that recorded number (a plain integer, an event index from section 4). Only now can we write the check.

Why the topic needs it: this single < comparison is the whistle. It converts the abstract "are they ordered?" into one integer test the runtime can do millions of times per second.
How the foundations feed the topic
The map below is not just a table of contents: follow the arrows and you are literally tracing how a stamp becomes a verdict. A write on a shared box creates an access; that access is an event with an index; the index gets remembered in a vector clock via synchronization merges; and the final < test turns two clocks into a yes/no race answer. Each arrow is a "feeds into," so a break anywhere upstream (say, no synchronization) is exactly what produces "concurrent" downstream.
Related building blocks
- Why an unsynchronized race is not just wrong but undefined: Undefined Behavior in C and C++.
- The correct tools that create happens-before arrows: Mutexes and Locks and std::atomic and Memory Ordering.
- The memory-level cousin sanitizer (a different fault class, incompatible in one binary): AddressSanitizer (ASan).
- The hardware rules behind visibility: Memory Models.