Note: this paper is superseded by [P1135r0], which takes the r1 version of this paper and integrates it fully with other related papers which should be voted into the working draft together.
1. Revision History
1.1. r0 ➡ r1
In Rapperswil, SG1 reviewed [P0995R0] at LEWG’s request.
Potential ABI breakage to achieve implementation efficiency was considered. SG1 is unanimously comfortable with this. Implementations can choose to avoid breakage and offer a less efficient implementation.
LEWG was unhappy about
/
being optional. SG1 was worried that some platforms would be unable to implement
them as lock-free because they lack a compare-and-exchange instruction and might
not be able to disable interrupts on all cores. After discussion, SG1 agreed to
make these mandatory despite rare platforms potentially being unable to
implement these types.
SF | F | N | A | SA | |
---|---|---|---|---|---|
/ should be mandatory
| 4 | 9 | 12 | 3 | 1 |
Move this paper to LEWG with intent to include in IS, either with or without the change above | 20 | 7 | 2 | 0 | 0 |
LEWG then saw the paper again, and there was unanimous consent to forward to LWG for C++20.
1.2. Draft ➡ r0
This paper was written in Jacksonville and presented to SG1, which unanimously forwarded the paper to LEWG. LEWG looked at the paper and took the following poll:
SF | F | N | A | SA | |
---|---|---|---|---|---|
Make the type aliases non-optional. | 1 | 4 | 4 | 2 | 2 |
The types were made optional in case an architecture, such as PA-RISC, cannot support always-lock-free integral types because no compare-and-exchange instruction is available. There was no consensus for making aliases required, though concern was expressed that LEWG doesn’t usually make functionality optional. In the C++ standard library optionality is present as follows:
-
/intN_t
are mandated by C, "if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the signed types) that have a two’s complement representation";uintN_t -
andabs
overloads "if and only if the typediv
designates an extended integer type";intmax_t -
The library
template has optional requirements.allocator_traits
There was also discussion about ABI breakage to
. An argument was
made that
should also be sized such that waiting on them is most
efficient (which would be an ABI breakage), and if that breakage doesn’t occur
then adding wait / notify overloads is actively misleading. LEWG want SG1 to
reconsider whether the overloads should be provided.
2. Introduction
C++11 added
to the language as the minimally-required class which
could be used to implement
on hardware which seemed relevant at the
time. Detailed
history can be found in [N2145], [N2324], and [N2393]. The specification was quite successful at minimalism—the only member
functions of
are
and
—but
was
wildly more successful and to our knowledge has always been implemented with
compiler support instead of with the very inefficient (but beautifully simple)
. Our experience is that
's interface is so minimal as
to be mostly useless, in particular it doesn’t have a method which can load the
flag’s value without modifying it.
We’ve heard of it being used as:
-
A questionable spinloop (as was originally intended);
-
A "check-in" flag used to know when at least one thread has reached a program location.
The one special power
has is in being the only type which is
guaranteed to be lock-free, albeit a mostly powerless one.
SG1 tried to salvage
in [P0514R0] by adding
,
,
,
, and
methods but decided to leave it as-is and
implement efficient waiting differently, eventually going for [P0514R3].
The time has come to thank
for serving its purpose as an
implementability stand-in, and help it find its true purpose. We propose:
-
Adding a
method to it as [P0514R0] did. This could technically forbids some ancestral processors from implementing modern C++, but these platforms already don’t support any C++.test -
Add
overloads to [P0514R3]'s waiting and notify functions.atomic_flag -
Add always-lock-free integral type aliases, which are encouraged to be sized such that waiting on them is most efficient.
3. Wording
Under Header
synopsis [atomics.syn] edit as follows:
// 32.3, type aliases // ... using atomic_signed_lock_free = see below ; using atomic_unsigned_lock_free = see below ; // 32.8, flag type and operations struct atomic_flag ; bool atomic_flag_test ( volatile atomic_flag * ) noexcept ; bool atomic_flag_test ( atomic_flag * ) noexcept ; bool atomic_flag_test_explicit ( volatile atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_explicit ( atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_and_set ( volatile atomic_flag * ) noexcept ; bool atomic_flag_test_and_set ( atomic_flag * ) noexcept ; bool atomic_flag_test_and_set_explicit ( volatile atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_and_set_explicit ( atomic_flag * , memory_order ) noexcept ; void atomic_flag_clear ( volatile atomic_flag * ) noexcept ; void atomic_flag_clear ( atomic_flag * ) noexcept ; void atomic_flag_clear_explicit ( volatile atomic_flag * , memory_order ) noexcept ; void atomic_flag_clear_explicit ( atomic_flag * , memory_order ) noexcept ; #define ATOMIC_FLAG_INIT see below // 32.10, waiting and notifying functions template < class T > void atomic_notify_one ( const volatile atomic < T >* ); template < class T > void atomic_notify_one ( const atomic < T >* ); void atomic_notify_one ( const volatile atomic_flag * ); void atomic_notify_one ( const atomic_flag * ); template < class T > void atomic_notify_all ( const volatile atomic < T >* ); template < class T > void atomic_notify_all ( const atomic < T >* ); void atomic_notify_all ( const volatile atomic_flag * ); void atomic_notify_all ( const atomic_flag * ); template < class T > void atomic_wait ( const volatile atomic < T >* , typename atomic < T >:: value_type ); template < class T > void atomic_wait ( const atomic < T >* , typename atomic < T >:: value_type ); void atomic_wait ( const volatile atomic_flag * , bool ); void atomic_wait ( const atomic_flag * , bool ); template < class T > void atomic_wait_explicit ( const volatile atomic < T >* , typename atomic < T >:: value_type , memory_order ); template < class T > void atomic_wait_explicit ( const atomic < T >* , typename atomic < T >:: value_type , memory_order ); void atomic_wait_explicit ( const volatile atomic_flag * , bool , memory_order ); void atomic_wait_explicit ( const atomic_flag * , bool , memory_order );
In Atomic operations library [atomics], under Type aliases [atomics.alias], edit as follows:
The type aliases
,
atomic_intN_t ,
atomic_uintN_t , and
atomic_intptr_t are defined if and only if
atomic_uintptr_t ,
intN_t ,
uintN_t , and
intptr_t are defined, respectively.
uintptr_t The type aliases
and
atomic_signed_lock_free are defined to be specializations of
atomic_unsigned_lock_free whose template arguments are integral types, respectively signed and unsigned, other than
atomic .
bool shall be
is_always_lock_free true
forand
atomic_signed_lock_free . An implementation should choose the integral specialization of
atomic_unsigned_lock_free for which the waiting and notifying functions are most efficient.
atomic
In Atomic operations library [atomics], under Flag type and operations [atomics.flag], edit as follows:
namespace std { struct atomic_flag { bool test ( memory_order = memory_order_seq_cst ) volatile noexcept ; bool test ( memory_order = memory_order_seq_cst ) noexcept ; bool test_and_set ( memory_order = memory_order_seq_cst ) volatile noexcept ; bool test_and_set ( memory_order = memory_order_seq_cst ) noexcept ; void clear ( memory_order = memory_order_seq_cst ) volatile noexcept ; void clear ( memory_order = memory_order_seq_cst ) noexcept ; atomic_flag () noexcept = default ; atomic_flag ( const atomic_flag & ) = delete ; atomic_flag & operator = ( const atomic_flag & ) = delete ; atomic_flag & operator = ( const atomic_flag & ) volatile = delete ; }; bool atomic_flag_test ( volatile atomic_flag * ) noexcept ; bool atomic_flag_test ( atomic_flag * ) noexcept ; bool atomic_flag_test_explicit ( volatile atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_explicit ( atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_and_set ( volatile atomic_flag * ) noexcept ; bool atomic_flag_test_and_set ( atomic_flag * ) noexcept ; bool atomic_flag_test_and_set_explicit ( volatile atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_and_set_explicit ( atomic_flag * , memory_order ) noexcept ; void atomic_flag_clear ( volatile atomic_flag * ) noexcept ; void atomic_flag_clear ( atomic_flag * ) noexcept ; void atomic_flag_clear_explicit ( volatile atomic_flag * , memory_order ) noexcept ; void atomic_flag_clear_explicit ( atomic_flag * , memory_order ) noexcept ; #define ATOMIC_FLAG_INIT see below } The
type provides the classic test-and-set functionality. It has two states, set and clear.
atomic_flag Operations on an object of type
shall be lock-free. [ Note: Hence the operations should also be address-free. —end note]
atomic_flag The
type is a standard-layout struct. It has a trivial default constructor and a trivial destructor.
atomic_flag The macro
shall be defined in such a way that it can be used to initialize an object of type
ATOMIC_FLAG_INIT to the clear state. The macro can be used in the form:
atomic_flag atomic_flag guard = ATOMIC_FLAG_INIT ; It is unspecified whether the macro can be used in other initialization contexts. For a complete static-duration object, that initialization shall be static. Unless initialized with
, it is unspecified whether an
ATOMIC_FLAG_INIT object has an initial state of set or clear.
atomic_flag bool atomic_flag_test ( volatile atomic_flag * object ) noexcept ; bool atomic_flag_test ( atomic_flag * object ) noexcept ; bool atomic_flag_test_explicit ( volatile atomic_flag * object , memory_order order ) noexcept ; bool atomic_flag_test_explicit ( atomic_flag * object , memory_order order ) noexcept ; bool atomic_flag :: test ( memory_order order = memory_order_seq_cst ) volatile noexcept ; bool atomic_flag :: test ( memory_order order = memory_order_seq_cst ) noexcept ; Requires: The
argument shall not be
order nor
memory_order_release .
memory_order_acq_rel Effects: Memory is affected according to the value of
.
order Returns: Atomically returns the value pointed to by
or
object .
this bool atomic_flag_test_and_set ( volatile atomic_flag * object ) noexcept ; bool atomic_flag_test_and_set ( atomic_flag * object ) noexcept ; bool atomic_flag_test_and_set_explicit ( volatile atomic_flag * object , memory_order order ) noexcept ; bool atomic_flag_test_and_set_explicit ( atomic_flag * object , memory_order order ) noexcept ; bool atomic_flag :: test_and_set ( memory_order order = memory_order_seq_cst ) volatile noexcept ; bool atomic_flag :: test_and_set ( memory_order order = memory_order_seq_cst ) noexcept ; Effects: Atomically sets the value pointed to by
or by
object to
this true
. Memory is affected according to the value of. These operations are atomic read-modify-write operations (4.7).
order Returns: Atomically, the value of the object immediately before the effects.
void atomic_flag_clear ( volatile atomic_flag * object ) noexcept ; void atomic_flag_clear ( atomic_flag * object ) noexcept ; void atomic_flag_clear_explicit ( volatile atomic_flag * object , memory_order order ) noexcept ; void atomic_flag_clear_explicit ( atomic_flag * object , memory_order order ) noexcept ; void atomic_flag :: clear ( memory_order order = memory_order_seq_cst ) volatile noexcept ; void atomic_flag :: clear ( memory_order order = memory_order_seq_cst ) noexcept ; Requires: The
argument shall not be
order ,
memory_order_consume , nor
memory_order_acquire .
memory_order_acq_rel Effects: Atomically sets the value pointed to by
or by
object to
this false
. Memory is affected according to the value of.
order
In Atomic operations library [atomics], under Waiting and notifying functions [atomics.wait], edit as follows:
The functions in this subclause provide a mechanism to wait for the value of an atomic object to change, more efficiently than can be achieved with polling. Waiting functions in this facility may block until they are unblocked by notifying functions, according to each function’s effects. [Note: Programs are not guaranteed to observe transient atomic values, an issue known as the A-B-A problem, resulting in continued blocking if a condition is only temporarily met. – End Note.]
The functions
and
atomic_wait are waiting functions. The functions
atomic_wait_explicit and
atomic_notify_one are notifying functions.
atomic_notify_all template < class T > void atomic_notify_one ( const volatile atomic < T >* object ); template < class T > void atomic_notify_one ( const atomic < T >* object ); void atomic_notify_one ( const volatile atomic_flag * object ); void atomic_notify_one ( const atomic_flag * object ); Effects: unblocks up to execution of a waiting function that blocked after observing the result of an atomic operation X, if there exists another atomic operation Y, such that X precedes Y in the modification order of
, and Y happens-before this call.
* object template < class T > void atomic_notify_all ( const volatile atomic < T >* object ); template < class T > void atomic_notify_all ( const atomic < T >* object ); void atomic_notify_all ( const volatile atomic_flag * object ); void atomic_notify_all ( const atomic_flag * object ); Effects: unblocks each execution of a waiting function that blocked after observing the result of an atomic operation X, if there exists another atomic operation Y, such that X precedes Y in the modification order of
, and Y happens-before this call.
* object template < class T > void atomic_wait_explicit ( const volatile atomic < T >* object , typename atomic < T >:: value_type old , memory_order order ); template < class T > void atomic_wait_explicit ( const atomic < T >* object , typename atomic < T >:: value_type old , memory_order order ); Requires: The order argument shall not be
nor
memory_order_release .
memory_order_acq_rel Effects: Repeatedly performs the following steps, in order:
Evaluates
then, if the result is
object -> load ( order ) != old true
, returns.Blocks until an implementation-defined condition has been met. [Note: Consequently, it may unblock for reasons other than a call to a notifying function. - end note]
void atomic_wait_explicit ( const volatile atomic_flag * object , bool old , memory_order order ); void atomic_wait_explicit ( const atomic_flag * object , bool old , memory_order order ); Effects: Repeatedly performs the following steps, in order:
Evaluates
then, if the result is
object -> test ( order ) != old true
, returns.Blocks until an implementation-defined condition has been met. [Note: Consequently, it may unblock for reasons other than a call to a notifying function. - end note]
template < class T > void atomic_wait ( const volatile atomic < T >* object , typename atomic < T >:: value_type old ); template < class T > void atomic_wait ( const atomic < T >* object , typename atomic < T >:: value_type old ); void atomic_wait ( const volatile atomic_flag * object , bool old ); void atomic_wait ( const atomic_flag * object , bool old ); Effects: Equivalent to:
atomic_wait_explicit ( object , old , memory_order_seq_cst );
Two feature test macros should be added:
-
implies the__cpp_lib_atomic_flag_test
methods fortest
and free functions, as well as the notify and wait overloads foratomic_flag
, are available.atomic_flag -
implies__cpp_lib_atomic_lock_free_type_aliases
andatomic_signed_lock_free
types are defined.atomic_unsigned_lock_free