A data race occurs when threads access the same memory location without an enforced ordering, at least one access is a write, and at least one access is non-atomic. It causes undefined behavior. A race condition is broader: it can arise when operations interleave in a way that breaks an invariant, even when it is not a data race. section 5.1.2 section 3.1.1
void undefined_behaviour_with_double_checked_locking()
{
if(!resource_ptr) #1
{
std::lock_guard<std::mutex> lk(resource_mutex);
if(!resource_ptr) #2
{
resource_ptr.reset(new some_resource); #3
}
}
resource_ptr->do_something(); #4
}
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":2,\"ch\":55},{\"line\":2,\"ch\":57}],[{\"line\":5,\"ch\":55},{\"line\":5,\"ch\":57}],[{\"line\":7,\"ch\":55},{\"line\":7,\"ch\":57}],[{\"line\":10,\"ch\":55},{\"line\":10,\"ch\":57}]]"}
!@%STYLE%@!
The first pointer check is outside the mutex. A thread can therefore read the pointer without synchronization with another thread’s initialization, risking a data race on both the pointer and the pointed-to object. section 3.3.1
Protect shared access with synchronization, or use atomic operations where appropriate. Atomic access can make behavior defined, but does not prevent competing operations or specify which occurs first. For one-time initialization, std::call_once provides synchronization between initialization and its callers. section 5.1.2 section 3.3.1
std::for_each(std::execution::par,v.begin(),v.end(),
[&](int& x){ x=++count; });
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":14},{\"line\":0,\"ch\":33}]]"}
!@%STYLE%@!
This parallel algorithm increments a shared count from multiple lambda invocations. That unsynchronized modification creates a data race; replacing count with an atomic or using a mutex can make access defined, though synchronization may serialize the calls. section 10.2.3



