A more detailed rationale for many of the decisions made here is given in P1875. Here we quickly summarize design decisions leading to this proposal. These are generally motivated by the desire to produce a much simpler, more easily implementable specification. Except for the last two, these have been stable since the original SG1 discussion. All were part of the proposal during the final EWG discussion.
malloc
(and hence non-escaping exceptions, and much more of the standard library) inside transactions.
This was a recent change, but preceded the last EWG discussion. We previously thought that we could get by with
very restricted transactions, so long as they were sufficiently general to implement N-way compare_exchange.
Closer examination by SG5 determined that was problematic since the load operations preceding the compare_exchange
transaction would also need to be transactions, making them expensive, at least without heroic implementation
effort. Thus we concluded that it was desirable to again allow much of the standard library inside
transactions. (Also discussed in more detail in P1875, but more as an open issue.)
In 5.10 [lex.name], add atomic
to table 4 [tab:lex.name.special].
Change in 6.9.1 [intro.execution] paragraph 5:
A full-expression isChange in 6.9.2.1 [intro.races] paragraph 6:
- ...
- an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (6.7.7) whose lifetime has not been extended,
or- the start and the end of an atomic block (8.8 [stmt.tx]), or
- an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
Atomic blocks as well asAdd a new paragraph after 6.9.2.1 [intro.races] paragraph 20:Certaincertain library calls may synchronize with other atomic blocks and library calls performed by another thread.
An atomic block that is not dynamically nested within another atomic block is termed a transaction. [Note: Due to syntactic constraints, blocks cannot overlap unless one is nested within the other.] There is a global total order of execution for all transactions. If, in that total order, a transaction T1 is ordered before a transaction T2, then[ Note: If the evaluations in T1 and T2 do not conflict, they might be executed concurrently. -- end note ]
- no evaluation in T2 happens before any evaluation in T1 and
- if T1 and T2 perform conflicting expression evaluations, then the end of T1 synchronizes with the start of T2.
Two actions are potentially concurrent if ...Change in 6.9.2.1 [intro.races] paragraph 21:
... [Note: It can be shown that programs that correctly use
mutexes, atomic blocks,
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". ...
Add a new paragraph after 6.9.2.1 [intro.races] paragraph 21:
[ Note: The following holds for a data-race-free program: If the start of an atomic block T is sequenced before an evaluation A, A is sequenced before the end of T, A strongly happens before some evaluation B, and B is not sequenced before the end of T, then the end of T strongly happens before B. If an evaluation C strongly happens before that evaluation A and C is not sequenced after the start of T, then C strongly happens before the start of T. These properties in turn imply that in any simple interleaved (sequentially consistent) execution, the operations of each atomic block appear to be contiguous in the interleaving. -- end note ]Change in 6.9.2.2 [intro.progress] paragraph 1:
Add a production to the grammar in 8.1 [expr.pre]:The implementation may assume that any thread will eventually doAn inter-thread side effect is one of the following:The implementation may assume that any thread will eventually terminate or evaluate an inter-thread side effect. [Note: This is intended to allow compiler transformations such as removal of empty loops, even when termination cannot be proven. — end note]
terminate,- a call to a library I/O function,
- an access through a volatile glvalue, or
- a synchronization operation or an atomic operation ([atomics]).
Add a new subclause before 8.8 [stmt.dcl]:statement: labeled-statement attribute-specifier-seqopt expression-statement attribute-specifier-seqopt compound-statement attribute-specifier-seqopt selection-statement attribute-specifier-seqopt iteration-statement attribute-specifier-seqopt jump-statement declaration-statement attribute-specifier-seqopt try-block atomic-statement
8.8 Atomic statement [stmt.tx]Add 16.4.6.17 [atomic.use]:
atomic-statement: atomic do compound-statementAn atomic-statement is also called an atomic block.The start of the atomic block is immediately before the opening
{
of the compound-statement. The end of the atomic block is immediately after the closing}
of the compound-statement. [ Note: Thus, variables with automatic storage duration declared in the compound-statement are destroyed prior to reaching the end of the atomic block; see 8.7 [stmt.jump]. -- end note ]A goto or switch statement shall not be used to transfer control into an atomic block.
If the execution of an atomic block evaluates an inter-thread side effect (6.9.2.2 [intro.progress]) or if an atomic block is exited via an exception, the behavior is undefined.
Recommended practice: In case an atomic block is exited via an exception, the program should be terminated without invoking a terminate handler (17.9.5 [exception.terminate]) or destroying any objects with static or thread storage duration (6.9.3.4 [basic.start.term]).
If the execution of an atomic block evaluates any of the following outside of a manifestly constant-evaluated context (7.7 [expr.const]), the behavior is implementation-defined:
[ Note: Some functions in the standard library can be used in an atomic block (16.4.6.17 [atomic.use]). ] [ Note: The implementation may define that the behavior is undefined in some or all of the cases above. ]
- an asm-declaration (9.10 [dcl.asm]);
- an invocation of a function other than an inline function with a reachable definition;
- a virtual function call (7.6.1.2 [expr.call]);
- a function call whose postfix-expression does not name a function (12.4.1.1 [over.match.call]);
- a
co_await
expression (7.6.2.3 [expr.await]), a yield-expression (7.6.17 [expr.yield]), or aco_return
statement (8.7.4 [stmt.return.coroutine]);- dynamic initialization of a block-scope variable with static storage duration; or
- dynamic initialization of a variable with thread storage duration.
[ Example:
int f() { static int i = 0; atomic do { ++i; return i; } }Each invocation of f (even when called from several threads simultaneously) retrieves a unique value (ignoring overflow). -- end example ][ Note: Atomic blocks are likely to perform best where they execute quickly and touch little data. -- end note ]
16.4.6.17 Functions usable in an atomic block [atomic.use]
All library functions may be used in an atomic block (8.8 [stmt.tx]), except
- time zone database ([time.zone.db])
- clocks ([time.clock])
signal
([support.signal])set_new_handler
,set_terminate
,get_new_handler
,get_terminate
[(handler.functions], [alloc.errors], [exception.syn])shared_ptr
([util.smartptr.shared]) andweak_ptr
([util.smartptr.weak])synchronized_pool_resource
([mem.res.pool])setjmp
/longjmp
([csetjmp.syn])locale
construction ([locale.cons])- input/output ([input.output])
- atomic operations ([atomics])
- thread support ([thread])