C++ has threading and shared access to variables which have the vulnerabilities described in ISO/IEC 24772-1 clause 6.61.1. Accessing mutable data from multiple threads without synchronization is undefined behaviour [EWF]. C++ provides atomic types, mutual exclusion primitives and higher-level synchronization mechanisms in the standard library to prevent such data races. Sharing non-volatile constant data or not sharing data at all does not incur data races. For example, avoiding pointer-like types for thread function parameters guarantees that there is no sharing of the argument values.
The C++ memory model defines how memory accesses can be reordered. The default memory order guarantees sequential consistency. For increased parallel execution other memory orders can be used but require expert-level of care.
NOTE: The above sentence needs explanation or rewrite.
C++ programs that used multiple threads before the C++11 standard might employ volatile variables to guarantee race free data access. This practice is not sanctioned by current standards, you cannot use volatile
for synchronization. The use of volatile std::sig_atomic_t
only prevents a data race between a signal handler an the execution thread running the signal handler.
The algorithm library allows specifying non-sequential execution policy for most algorithms. Employing parallel (std::par
) or vectorized (std::unseq
, std::par_unseq
) execution policies may have additional requirements on data layout of the underlying ranges, especially to avoid data races.
Primitives requiring explicit locking and unlocking the pairwise application of these operations is supported implicitly by std::scoped_lock
, even when an exception might be thrown. In addition to scope-based locking, std::scoped_lock
guarantees deadlock-free locking of multiple mutexes.
Variables with thread-local storage duration cannot create data races, as long as their address is not shared with other threads.
Discuss sharing of memory between tasks!! i.e. Don’t do it!?
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Place all mutable data in memory accessible to only one thread at a time;
Use the appropriate standard library features for synchronization of shared mutable data, in particular use std::scoped_lock
or the other scoped-based locking library facilities;
Prohibit calling lock and unlock facilities directly.
Forbid the use of volatile for synchronization;
Avoid specifying memory orders on atomic operations other than sequential consistency;
Use atomic variables or fences for synchronization of data shared from a signal handler, employ volatile sig_atomic_t
only in a single-threaded environment;
Pass all thread function arguments by non-pointer-like values.