a data race
A data race is a specific, technical kind of trouble, narrower than the general race condition. It happens when two or more threads access the same memory location at the same time, at least one of those accesses is a write, and there is nothing forcing an order between them (no synchronization). Picture two people writing different words into the same blank on a form simultaneously - the ink smears and you cannot say what the blank now holds. In C and C++ this is not merely 'undefined what you get'; the standard declares a data race to be undefined behavior, which is far worse.
Let us be precise about the three conditions, because all three must hold. First, two threads touch the same location - the same variable, the same byte of a shared object. Second, at least one access is a write; two threads only reading the same data never race. Third, the accesses are concurrent and unsynchronized - nothing (no mutex, no atomic, no other ordering guarantee) establishes that one happens before the other. When all three hold, the program has a data race. Note the contrast with a race condition: a race condition is any timing-dependent correctness bug; a data race is the specific low-level case of unsynchronized conflicting memory access. Every data race is a race condition, but a race condition can exist with no data race at all (for instance, a 'check then act' built entirely from individually safe operations).
Why this is so dangerous in C and C++: because the standard calls a data race undefined behavior, the compiler is allowed to assume it never happens. It may then optimize in ways that make the bug bizarre - reordering instructions, caching a shared variable in a register so updates from another thread are never seen, or producing 'impossible' values. So a data race is not just 'you might read a slightly stale number'; it can corrupt the program in ways that defeat your reasoning entirely. The only correct fix is to remove the race - synchronize the conflicting accesses (a lock or an atomic), so the conflict can no longer occur. A tool called a thread sanitizer can detect many data races at run time and is well worth using.
Two threads each do shared_counter++ on a plain int with no lock. That is a data race: same location, both writing, no synchronization - undefined behavior in C. Making shared_counter an atomic, or guarding it with a mutex, removes the race.
Same location + at least one write + no synchronization = a data race = undefined behavior in C/C++.
Because a data race is undefined behavior, 'it seemed to work' means nothing - the compiler may have optimized your code assuming the race could not happen, so the failure may appear far from the racing line, or only at -O2. Do not 'fix' it with a sleep() or by reordering; only real synchronization removes it.