The library defines a number of atomic operations (
[atomics]) and
operations on mutexes (
[thread]) that are specially identified as
synchronization operations
. These operations play a special role in making
assignments in one thread visible to another
. A synchronization operation on one
or more memory locations is either an acquire operation, a
release operation, or both an acquire and release operation
. A synchronization
operation without an associated memory location is a fence and can be either an
acquire fence, a release fence, or both an acquire and release fence
. In
addition, there are relaxed atomic operations, which are not synchronization
operations, and atomic read-modify-write operations, which have special
characteristics
. [
Note 3:
For example, a call that acquires a mutex will
perform an acquire operation on the locations comprising the mutex
. Correspondingly, a call that releases the same mutex will perform a release
operation on those same locations
. Informally, performing a release operation on
A forces prior
side effects on other memory locations to become visible
to other threads that later perform a consume or an acquire operation on
A. “Relaxed” atomic operations are not synchronization operations even
though, like synchronization operations, they cannot contribute to data races
. —
end note]
All modifications to a particular atomic object
M occur in some
particular total order, called the
modification order of
M. [
Note 4:
There is a separate order for each
atomic object
. There is no requirement that these can be combined into a single
total order for all objects
. In general this will be impossible since different
threads can observe modifications to different objects in inconsistent orders
. —
end note]
A
release sequence headed
by a release operation
A on an atomic object
M
is a maximal contiguous sub-sequence of
side effects in the modification order of
M,
where the first operation is
A, and
every subsequent operation is an atomic read-modify-write operation
.Certain library calls
synchronize with other library calls performed by
another thread
. For example, an atomic store-release synchronizes with a
load-acquire that takes its value from the store (
[atomics.order])
. [
Note 5:
Except in the specified cases, reading a later value does not
necessarily ensure visibility as described below
. Such a requirement would
sometimes interfere with efficient implementation
. —
end note]
[
Note 6:
The
specifications of the synchronization operations define when one reads the value
written by another
. For atomic objects, the definition is clear
. All operations
on a given mutex occur in a single total order
. Each mutex acquisition “reads
the value written” by the last mutex release
. —
end note]
An evaluation
A happens before an evaluation
B
(or, equivalently,
B happens after
A)
if either
- A is sequenced before B, or
- A synchronizes with B, or
- A happens before X and X happens before B.
[
Note 7:
An evaluation does not happen before itself
. —
end note]
An evaluation
A strongly happens before
an evaluation
D if, either
- A is sequenced before D, or
- A synchronizes with D, and
both A and D are
sequentially consistent atomic operations ([atomics.order]), or
- there are evaluations B and C
such that A is sequenced before B,
B happens before C, and
C is sequenced before D, or
- there is an evaluation B such that
A strongly happens before B, and
B strongly happens before D.
[
Note 8:
Informally, if
A strongly happens before
B,
then
A appears to be evaluated before
B
in all contexts
. —
end note]
A
visible side effect A on a scalar object or bit-field
M
with respect to a value computation
B of
M satisfies the
conditions:
- A happens before B and
- there is no other
side effect X to M such that A
happens before X and X happens before B.
The value of a non-atomic scalar object or bit-field
M, as determined by
evaluation
B, is the value stored by the
visible side effect
A. [
Note 9:
If there is ambiguity about which side effect to a
non-atomic object or bit-field is visible, then the behavior is either
unspecified or undefined
. —
end note]
[
Note 10:
This states that operations on
ordinary objects are not visibly reordered
. This is not actually detectable
without data races, but is needed to ensure that data races, as defined
below, and with suitable restrictions on the use of atomics, correspond to data
races in a simple interleaved (sequentially consistent) execution
. —
end note]
The value of an
atomic object
M, as determined by evaluation
B, is the value
stored by some unspecified
side effect
A that modifies
M, where
B does not happen
before
A. [
Note 11:
The set of such side effects is also restricted by the rest of the rules
described here, and in particular, by the coherence requirements below
. —
end note]
If an operation
A that modifies an atomic object
M happens before
an operation
B that modifies
M, then
A is earlier
than
B in the modification order of
M. [
Note 12:
This requirement is known as write-write coherence
. —
end note]
If a
value computation
A of an atomic object
M happens before a
value computation
B of
M, and
A takes its value from a side
effect
X on
M, then the value computed by
B is either
the value stored by
X or the value stored by a
side effect
Y on
M,
where
Y follows
X in the modification order of
M. [
Note 13:
This requirement is known as read-read coherence
. —
end note]
If a
value computation
A of an atomic object
M happens before an
operation
B that modifies
M, then
A takes its value from a side
effect
X on
M, where
X precedes
B in the
modification order of
M. [
Note 14:
This requirement is known as
read-write coherence
. —
end note]
If a
side effect
X on an atomic object
M happens before a value
computation
B of
M, then the evaluation
B takes its
value from
X or from a
side effect
Y that follows
X in the modification order of
M. [
Note 15:
This requirement is known as write-read coherence
. —
end note]
[
Note 16:
The four preceding coherence requirements effectively disallow
compiler reordering of atomic operations to a single object, even if both
operations are relaxed loads
. This effectively makes the cache coherence
guarantee provided by most hardware available to C++ atomic operations
. —
end note]
[
Note 17:
The value observed by a load of an atomic depends on the “happens
before” relation, which depends on the values observed by loads of atomics
. The intended reading is that there must exist an
association of atomic loads with modifications they observe that, together with
suitably chosen modification orders and the “happens before” relation derived
as described above, satisfy the resulting constraints as imposed here
. —
end note]
Two actions are
potentially concurrent if
- they are performed by different threads, or
- they are unsequenced, at least one is performed by a signal handler, and
they are not both performed by the same signal handler invocation.
The execution of a program contains a
data race if it contains two
potentially concurrent conflicting actions, at least one of which is not atomic,
and neither happens before the other,
except for the special case for signal handlers described below
. Any such data race results in undefined
behavior
. [
Note 18:
It can be shown that programs that correctly use mutexes
and
memory_order::seq_cst operations to prevent all data races and use no
other synchronization operations behave as if the operations executed by their
constituent threads were simply interleaved, with each
value computation of an
object being taken from the last
side effect on that object in that
interleaving
. This is normally referred to as “sequential consistency”
. However, this applies only to data-race-free programs, and data-race-free
programs cannot observe most program transformations that do not change
single-threaded program semantics
. In fact, most single-threaded program
transformations remain possible, since any program that behaves
differently as a result has undefined behavior
. —
end note]
Two accesses to the same non-bit-field object
of type
volatile std::sig_atomic_t do not
result in a data race if both occur in the same thread, even if one or more
occurs in a signal handler. For each signal handler invocation, evaluations
performed by the thread invoking a signal handler can be divided into two
groups
A and
B, such that no evaluations in
B happen before evaluations in
A, and the
evaluations of such
volatile std::sig_atomic_t objects take values as though
all evaluations in A happened before the execution of the signal
handler and the execution of the signal handler happened before all evaluations
in B.[
Note 19:
Compiler transformations that introduce assignments to a potentially
shared memory location that would not be modified by the abstract machine are
generally precluded by this document, since such an assignment might overwrite
another assignment by a different thread in cases in which an abstract machine
execution would not have encountered a data race
. This includes implementations
of data member assignment that overwrite adjacent members in separate memory
locations
. Reordering of atomic loads in cases in which the atomics in question
might alias is also generally precluded, since this could violate the coherence
rules
. —
end note]
[
Note 20:
It is possible that transformations that introduce a speculative read of a potentially
shared memory location do not preserve the semantics of the C++ program as
defined in this document, since they potentially introduce a data race
. However,
they are typically valid in the context of an optimizing compiler that targets a
specific machine with well-defined semantics for data races
. They would be
invalid for a hypothetical machine that is not tolerant of races or provides
hardware race detection
. —
end note]