1. Introduction
This paper is the unification of a series of related C++20 proposals for introducing new synchronization and thread coordination facilities and enhancing existing ones:
-
[P0514R4]: Efficient
waiting and semaphores.atomic -
[P0666R2]: Latches and barriers.
-
[P0995R1]:
and lockfree integral types.atomic_flag :: test -
[P1258R0]: Don’t make C++ unimplementable for small CPUs.
2. Changelog
Revision 0: Post Rapperswil 2018 changes from [P0514R4], [P0666R2], and [P0995R1] based on Rapperswil 2018 LEWG feedback.
-
Refactored
andbasic_barrier
into one class with a default template parameter as suggested by LEWG at Rapperswil 2018.barrier -
Refactored
andbasic_semaphore
into one class with a default template parameter as suggested by LEWG at Rapperswil 2018.counting_semaphore -
Fixed
parameters in semaphore, latch, and barrier member functions to consistently default to 1 to resolve mistakes identified by LEWG at Rapperswil 2018.update
Revision 1: Pre San Diego 2018 changes based on Rapperswil 2018 LEWG feedback and a June discussion on the LEWG and SG1 mailing lists.
-
Added member function versions of
andatomic_wait_ *
, for consistency. Refactored wording to accommodate this.atomic_notify_ * -
Renamed the
overloads ofatomic_flag
andatomic_wait
toatomic_wait_explicit
andatomic_flag_wait
for consistency and to leave the door open for future compatibility with C.atomic_flag_wait_explicit -
Renamed
andlatch :: arrive_and_wait
tobarrier :: arrive_and_wait
andlatch :: sync
, because LEWG at Rapperswil 2018 expected these methods to be the common use case and prefers they have a short name.barrier :: sync -
Renamed
tolatch :: arrive
to further separate and distinguish thelatch :: count_down
andlatch
interfaces.barrier -
Removed
to resolve concerns raised during LEWG discussion at Rapperswil 2018 regarding its "maybe consuming" nature.barrier :: try_wait -
Required that
's move constructor and move assignment operators arebarrier :: arrival_token
to resolve discussions in LEWG at Rapperswil 2018 regarding exceptions being thrown when using the split arrive and wait barrier interface.noexcept -
Made
,counting_semaphore :: acquire
, andcounting_semaphore :: try_acquire latch :: wait
, because participants in the mailing list discussion preferred that synchronization operations not throw and that any resource acquisition failures be reported by throwing during construction of synchronization objects.noexcept -
Made
,counting_semaphore
, andlatch
's constructors nonbarrier
and allowed them to throwconstexpr
if the object cannot be created, because participants in the mailing list discussion preferred that synchronization operations not throw and that any resource acquisition failures be reported by throwing during construction of synchronization objects.system_error -
Clarified that
,counting_semaphore :: release
,latch :: count_down
,latch :: sync
,barrier :: wait
, andbarrier :: sync
throw nothing (but cannot bebarrier :: arrive_and_drop
, because they have preconditions) to resolve discussions in LEWG at Rapperswil 2018 and on the mailing list.noexcept
Revision 2: San Diego 2018 changes to incorporate [P1258R0] and pre-meeting feedback.
-
Made
take itsbarrier :: wait
parameter by rvalue reference.arrival_token -
Made the
andatomic_signed_lock_free
types optional for freestanding implementations, as per [P1258R0].atomic_unsigned_lock_free
Revision 3: Pre Kona 2019 changes based on San Diego 2018 LEWG feedback.
-
Renamed
andlatch :: sync
back tobarrier :: sync
andlatch :: arrive_and_wait
, because this name had the strongest consensus in LEWG at San Diego 2018.barrier :: arrive_and_wait -
Removed
andatomic_int_fast_wait_t
, because LEWG at San Diego 2018 felt that the use case was uncommon and the types had high potential for misuse.atomic_uint_fast_wait_t -
Made
andcounting_semaphore :: acquire
nonlatch :: wait
again, because LEWG at San Diego 2018 desirednoexcept
constructors for new synchronization objects to allow synchronization during program initialization and to maintain consistency with existing synchronization objects likeconstexpr
.mutex -
Made
,counting_semaphore
, andlatch
's constructorsbarrier
again, because LEWG at San Diego 2018 desiredconstexpr
constructors for new synchronization objects to allow synchronization during program initialization and to maintain consistency with existing synchronization objects likeconstexpr
.mutex -
Clarified that
,counting_semaphore :: release
,latch :: count_down
,latch :: arrive_and_wait
,barrier :: wait
, andbarrier :: arrive_and_wait
may throwbarrier :: arrive_and_drop
exceptions, which is an implication of the constructors of said objects beingsystem_error
because any underlying system errors must be reported on operations not during construction.constexpr -
Added missing
andatomic < T >:: wait
member functions to the class synopses for theatomic < T >:: notify_ *
integral, floating-point, and pointer specializations.atomic < T > -
Fixed
member functions to be nonatomic < T >:: notify_ *
.const
Revision 4: Lots of wording changes based on Kona 2019 LWG feedback. Three design changes to fix bugs that were discovered during LWG review or afterwards while revising the paper. These will be presented to SG1 in a separate paper (yet to be written) in Cologne.
-
Changed
to be aatomic_flag :: test
function. Changed theconst
parameter ofatomic_flag *
andatomic_flag_test
to beatomic_flag_test_explicit
.const atomic_flag * -
Added the requirement that the
template parameter toleast_max_value
be greater than zero.counting_iterator -
Changed the requirement on the
parameter toupdate
frombarrier :: arrive
toupdate >= 0
.update > 0
3. Wording
Note: The following changes are relative to the post San Diego 2018 working draft of ISO/IEC 14882, ([N4791]).
Note: The � character is used to denote a placeholder number which shall be selected by the editor.
Add
,
, and
to Table 19 "C++ library headers" in [headers].
Modify the header synopsis for
in [atomics.syn] as follows:
30.2 Headersynopsis [atomics.syn]
< atomic >
namespace std { // ... // 30.8, non-member functions // ...
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 );
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 );
template < class T >
void atomic_notify_one ( volatile atomic < T >* );
template < class T >
void atomic_notify_one ( atomic < T >* );
template < class T >
void atomic_notify_all ( volatile atomic < T >* );
template < class T >
void atomic_notify_all ( atomic < T >* );
// 30.3, type aliases // ... using atomic_intptr_t = atomic < intptr_t > ; using atomic_uintptr_t = atomic < uintptr_t > ; using atomic_size_t = atomic < size_t > ; using atomic_ptrdiff_t = atomic < ptrdiff_t > ; using atomic_intmax_t = atomic < intmax_t > ; using atomic_uintmax_t = atomic < uintmax_t > ;
using atomic_signed_lock_free = see below ;
using atomic_unsigned_lock_free = see below ;
// ... // 30.9, flag type and operations struct atomic_flag ; bool atomic_flag_test ( const volatile atomic_flag * ) noexcept ; bool atomic_flag_test ( const atomic_flag * ) noexcept ; bool atomic_flag_test_explicit ( const volatile atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_explicit ( const 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 ;
void atomic_flag_wait ( const volatile atomic_flag * , bool ) noexcept ;
void atomic_flag_wait ( const atomic_flag * , bool ) noexcept ;
void atomic_flag_wait_explicit ( const volatile atomic_flag * , bool , memory_order ) noexcept ;
void atomic_flag_wait_explicit ( const atomic_flag * , bool , memory_order ) noexcept ;
void atomic_flag_notify_one ( volatile atomic_flag * ) noexcept ;
void atomic_flag_notify_one ( atomic_flag * ) noexcept ;
void atomic_flag_notify_all ( volatile atomic_flag * ) const noexcept ;
void atomic_flag_notify_all ( atomic_flag * ) const noexcept ; #
define ATOMIC_FLAG_INIT see below // 30.10, fences extern "C" void atomic_thread_fence ( memory_order ) noexcept ; extern "C" void atomic_signal_fence ( memory_order ) noexcept ; }
Modify [atomics.alias] as follows:
30.3 Type aliases [atomics.alias]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 aliasesand
atomic_signed_lock_free are specializations of
atomic_unsigned_lock_free whose template arguments are integral types, respectively signed and unsigned, other than
atomic . In freestanding implementations (4.1), these aliases are optional. Only implementations that provide an integral specialization of
bool other than
atomic for which
bool is true, also provide
is_always_lock_free and
atomic_signed_lock_free .
atomic_unsigned_lock_free is
is_always_lock_free true
forand
atomic_signed_lock_free . [ Note: An implementation which provides these type aliases should choose the integral specialization of
atomic_unsigned_lock_free for which the atomic waiting and notifying operations are most efficient. - end note ]
atomic
Note: The reference to "atomic waiting and notifying operations" in the above change should refer to the new [atomics.wait] subclause.
Add a new subclause after [atomics.lockfree]:
30.� Waiting and notifying [atomics.wait]Atomic waiting and notifying operations provide a mechanism to wait for the value of an atomic object to change more efficiently than can be achieved with polling.The following functions are atomic waiting operations:
.
atomic < T >:: wait
.
atomic_flag :: wait
and
atomic_wait .
atomic_wait_explicit
and
atomic_flag_wait .
atomic_flag_wait_explicit The following functions are atomic notifying operations:
and
atomic < T >:: notify_one .
atomic < T >:: notify_all
and
atomic_flag :: notify_one .
atomic_flag :: notify_all
and
atomic_notify_one .
atomic_notify_all
and
atomic_flag_notify_one .
atomic_flag_notify_all Atomic waiting operations may block until they are unblocked by atomic notifying operations, 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 ]
Modify [atomics.types.generic] as follows:
30.7 Class template[atomics.types.generic]
atomic
namespace std { template < class T > struct atomic { using value_type = T ; static constexpr bool is_always_lock_free = implementation - defined ; bool is_lock_free () const volatile noexcept ; bool is_lock_free () const noexcept ; void store ( T , memory_order = memory_order :: seq_cst ) volatile noexcept ; void store ( T , memory_order = memory_order :: seq_cst ) noexcept ; T load ( memory_order = memory_order :: seq_cst ) const volatile noexcept ; T load ( memory_order = memory_order :: seq_cst ) const noexcept ; operator T () const volatile noexcept ; operator T () const noexcept ; T exchange ( T , memory_order = memory_order :: seq_cst ) volatile noexcept ; T exchange ( T , memory_order = memory_order :: seq_cst ) noexcept ; bool compare_exchange_weak ( T & , T , memory_order , memory_order ) volatile noexcept ; bool compare_exchange_weak ( T & , T , memory_order , memory_order ) noexcept ; bool compare_exchange_strong ( T & , T , memory_order , memory_order ) volatile noexcept ; bool compare_exchange_strong ( T & , T , memory_order , memory_order ) noexcept ; bool compare_exchange_weak ( T & , T , memory_order = memory_order :: seq_cst ) volatile noexcept ; bool compare_exchange_weak ( T & , T , memory_order = memory_order :: seq_cst ) noexcept ; bool compare_exchange_strong ( T & , T , memory_order = memory_order :: seq_cst ) volatile noexcept ; bool compare_exchange_strong ( T & , T , memory_order = memory_order :: seq_cst ) noexcept ;
void wait ( T , memory_order = memory_order :: seq_cst ) const volatile noexcept ;
void wait ( T , memory_order = memory_order :: seq_cst ) const noexcept ;
void notify_one () volatile noexcept ;
void notify_one () noexcept ;
void notify_all () volatile noexcept ;
void notify_all () noexcept ;
atomic () noexcept = default ; constexpr atomic ( T ) noexcept ; atomic ( const atomic & ) = delete ; atomic & operator = ( const atomic & ) = delete ; atomic & operator = ( const atomic & ) volatile = delete ; T operator = ( T ) volatile noexcept ; T operator = ( T ) noexcept ; }; }
Add the following to the end of [atomics.types.operations]:
void wait ( T old , memory_order order = memory_order :: seq_cst ) const volatile noexcept ; void wait ( T old , memory_order order = memory_order :: seq_cst ) const noexcept ; Expects:is neither
order nor
memory_order :: release .
memory_order :: acq_rel Effects: Repeatedly performs the following steps, in order:
Evaluates
and compares its value representation for equality against that of
load ( order ) .
old If they compare unequal, returns.
Blocks until it is unblocked by an atomic notifying operation or is unblocked spuriously.
Remarks: This function is an atomic waiting operation.void notify_one () volatile noexcept ; void notify_one () noexcept ; Effects: Given the set W of atomic waiting operations such that:
each atomic waiting operation has blocked after observing the result of some atomic operation
,
X
precedes some atomic operation
X in the modification order of
Y , and
* this
happens before this call.
Y If the set W is not empty, unblocks the execution of one operation in W.
Remarks: This function is an atomic notifying operation.void notify_all () volatile noexcept ; void notify_all () noexcept ; Effects: Unblocks the execution of all atomic waiting operations such that:
each atomic waiting operation has blocked after observing the result of some atomic operation
,
X
precedes some atomic operation
X in the modification order of
Y , and
* this
happens before this call.
Y Remarks: This function is an atomic notifying operation.
Modify [atomics.types.int] paragraph 1 as follows:
30.7.2 Specializations for integers [atomics.types.int]There are specializations of theclass template for the integral types
atomic ,
char ,
signed char ,
unsigned char ,
short ,
unsigned short ,
int ,
unsigned int ,
long ,
unsigned long ,
long long ,
unsigned long long ,
char8_t ,
char16_t ,
char32_t , and any other types needed by the typedefs in the header
wchar_t . For each such type
< cstdint > , the specialization
integral provides additional atomic operations appropriate to integral types. [ Note: For the specialization
atomic < integral > , see 30.7. — end note ]
atomic < bool >
namespace std { template <> struct atomic < integral > { using value_type = integral ; using difference_type = value_type ; static constexpr bool is_always_lock_free = implementation - defined ; bool is_lock_free () const volatile noexcept ; bool is_lock_free () const noexcept ; void store ( integral , memory_order = memory_order :: seq_cst ) volatile noexcept ; void store ( integral , memory_order = memory_order :: seq_cst ) noexcept ; integral load ( memory_order = memory_order :: seq_cst ) const volatile noexcept ; integral load ( memory_order = memory_order :: seq_cst ) const noexcept ; operator integral () const volatile noexcept ; operator integral () const noexcept ; integral exchange ( integral , memory_order = memory_order :: seq_cst ) volatile noexcept ; integral exchange ( integral , memory_order = memory_order :: seq_cst ) noexcept ; bool compare_exchange_weak ( integral & , integral , memory_order , memory_order ) volatile noexcept ; bool compare_exchange_weak ( integral & , integral , memory_order , memory_order ) noexcept ; bool compare_exchange_strong ( integral & , integral , memory_order , memory_order ) volatile noexcept ; bool compare_exchange_strong ( integral & , integral , memory_order , memory_order ) noexcept ; bool compare_exchange_weak ( integral & , integral , memory_order = memory_order :: seq_cst ) volatile noexcept ; bool compare_exchange_weak ( integral & , integral , memory_order = memory_order :: seq_cst ) noexcept ; bool compare_exchange_strong ( integral & , integral , memory_order = memory_order :: seq_cst ) volatile noexcept ; bool compare_exchange_strong ( integral & , integral , memory_order = memory_order :: seq_cst ) noexcept ;
void wait ( integral , memory_order = memory_order :: seq_cst ) const volatile noexcept ;
void wait ( integral , memory_order = memory_order :: seq_cst ) const noexcept ;
void notify_one () volatile noexcept ;
void notify_one () noexcept ;
void notify_all () volatile noexcept ;
void notify_all () noexcept ;
integral fetch_add ( integral , memory_order = memory_order :: seq_cst ) volatile noexcept ; integral fetch_add ( integral , memory_order = memory_order :: seq_cst ) noexcept ; integral fetch_sub ( integral , memory_order = memory_order :: seq_cst ) volatile noexcept ; integral fetch_sub ( integral , memory_order = memory_order :: seq_cst ) noexcept ; integral fetch_and ( integral , memory_order = memory_order :: seq_cst ) volatile noexcept ; integral fetch_and ( integral , memory_order = memory_order :: seq_cst ) noexcept ; integral fetch_or ( integral , memory_order = memory_order :: seq_cst ) volatile noexcept ; integral fetch_or ( integral , memory_order = memory_order :: seq_cst ) noexcept ; integral fetch_xor ( integral , memory_order = memory_order :: seq_cst ) volatile noexcept ; integral fetch_xor ( integral , memory_order = memory_order :: seq_cst ) noexcept ; atomic () noexcept = default ; constexpr atomic ( integral ) noexcept ; atomic ( const atomic & ) = delete ; atomic & operator = ( const atomic & ) = delete ; atomic & operator = ( const atomic & ) volatile = delete ; integral operator = ( integral ) volatile noexcept ; integral operator = ( integral ) noexcept ; integral operator ++ ( int ) volatile noexcept ; integral operator ++ ( int ) noexcept ; integral operator -- ( int ) volatile noexcept ; integral operator -- ( int ) noexcept ; integral operator ++ () volatile noexcept ; integral operator ++ () noexcept ; integral operator -- () volatile noexcept ; integral operator -- () noexcept ; integral operator += ( integral ) volatile noexcept ; integral operator += ( integral ) noexcept ; integral operator -= ( integral ) volatile noexcept ; integral operator -= ( integral ) noexcept ; integral operator &= ( integral ) volatile noexcept ; integral operator &= ( integral ) noexcept ; integral operator |= ( integral ) volatile noexcept ; integral operator |= ( integral ) noexcept ; integral operator ^= ( integral ) volatile noexcept ; integral operator ^= ( integral ) noexcept ; }; }
Modify [atomics.types.float] paragraph 1 as follows:
30.7.3 Specializations for floating-point types [atomics.types.float]There are specializations of theclass template for the floating-point types
atomic ,
float , and
double . For each such type
long double , the specialization
floating - point provides additional atomic operations appropriate to floating-point types.
atomic < floating - point >
namespace std { template <> struct atomic < floating - point > { using value_type = floating - point ; using difference_type = value_type ; static constexpr bool is_always_lock_free = implementation - defined ; bool is_lock_free () const volatile noexcept ; bool is_lock_free () const noexcept ; void store ( floating - point , memory_order = memory_order :: seq_cst ) volatile noexcept ; void store ( floating - point , memory_order = memory_order :: seq_cst ) noexcept ; floating - point load ( memory_order = memory_order :: seq_cst ) const volatile noexcept ; floating - point load ( memory_order = memory_order :: seq_cst ) const noexcept ; operator floating - point () const volatile noexcept ; operator floating - point () const noexcept ; floating - point exchange ( floating - point , memory_order = memory_order :: seq_cst ) volatile noexcept ; floating - point exchange ( floating - point , memory_order = memory_order :: seq_cst ) noexcept ; bool compare_exchange_weak ( floating - point & , floating - point , memory_order , memory_order ) volatile noexcept ; bool compare_exchange_weak ( floating - point & , floating - point , memory_order , memory_order ) noexcept ; bool compare_exchange_strong ( floating - point & , floating - point , memory_order , memory_order ) volatile noexcept ; bool compare_exchange_strong ( floating - point & , floating - point , memory_order , memory_order ) noexcept ; bool compare_exchange_weak ( floating - point & , floating - point , memory_order = memory_order :: seq_cst ) volatile noexcept ; bool compare_exchange_weak ( floating - point & , floating - point , memory_order = memory_order :: seq_cst ) noexcept ; bool compare_exchange_strong ( floating - point & , floating - point , memory_order = memory_order :: seq_cst ) volatile noexcept ; bool compare_exchange_strong ( floating - point & , floating - point , memory_order = memory_order :: seq_cst ) noexcept ;
void wait ( floating - point , memory_order = memory_order :: seq_cst ) const volatile noexcept ;
void wait ( floating - point , memory_order = memory_order :: seq_cst ) const noexcept ;
void notify_one () volatile noexcept ;
void notify_one () noexcept ;
void notify_all () volatile noexcept ;
void notify_all () noexcept ;
floating - point fetch_add ( floating - point , memory_order = memory_order :: seq_cst ) volatile noexcept ; floating - point fetch_add ( floating - point , memory_order = memory_order :: seq_cst ) noexcept ; floating - point fetch_sub ( floating - point , memory_order = memory_order :: seq_cst ) volatile noexcept ; floating - point fetch_sub ( floating - point , memory_order = memory_order :: seq_cst ) noexcept ; atomic () noexcept = default ; constexpr atomic ( floating - point ) noexcept ; atomic ( const atomic & ) = delete ; atomic & operator = ( const atomic & ) = delete ; atomic & operator = ( const atomic & ) volatile = delete ; floating - point operator = ( floating - point ) volatile noexcept ; floating - point operator = ( floating - point ) noexcept ; floating - point operator += ( floating - point ) volatile noexcept ; floating - point operator += ( floating - point ) noexcept ; floating - point operator -= ( floating - point ) volatile noexcept ; floating - point operator -= ( floating - point ) noexcept ; }; }
Modify [atomics.types.pointer] paragraph 1 as follows:
30.7.4 Partial specialization for pointers [atomics.types.pointer]
namespace std { template < class T > struct atomic < T *> { using value_type = T * ; using difference_type = ptrdiff_t ; static constexpr bool is_always_lock_free = implementation - defined ; bool is_lock_free () const volatile noexcept ; bool is_lock_free () const noexcept ; void store ( T * , memory_order = memory_order :: seq_cst ) volatile noexcept ; void store ( T * , memory_order = memory_order :: seq_cst ) noexcept ; T * load ( memory_order = memory_order :: seq_cst ) const volatile noexcept ; T * load ( memory_order = memory_order :: seq_cst ) const noexcept ; operator T * () const volatile noexcept ; operator T * () const noexcept ; T * exchange ( T * , memory_order = memory_order :: seq_cst ) volatile noexcept ; T * exchange ( T * , memory_order = memory_order :: seq_cst ) noexcept ; bool compare_exchange_weak ( T *& , T * , memory_order , memory_order ) volatile noexcept ; bool compare_exchange_weak ( T *& , T * , memory_order , memory_order ) noexcept ; bool compare_exchange_strong ( T *& , T * , memory_order , memory_order ) noexcept ; bool compare_exchange_weak ( T *& , T * , memory_order = memory_order :: seq_cst ) volatile noexcept ; bool compare_exchange_weak ( T *& , T * , memory_order = memory_order :: seq_cst ) noexcept ; bool compare_exchange_strong ( T *& , T * , memory_order = memory_order :: seq_cst ) volatile noexcept ; bool compare_exchange_strong ( T *& , T * , memory_order = memory_order :: seq_cst ) noexcept ;
void wait ( T * , memory_order = memory_order :: seq_cst ) const volatile noexcept ;
void wait ( T * , memory_order = memory_order :: seq_cst ) const noexcept ;
void notify_one () volatile noexcept ;
void notify_one () noexcept ;
void notify_all () volatile noexcept ;
void notify_all () noexcept ;
T * fetch_add ( ptrdiff_t , memory_order = memory_order :: seq_cst ) volatile noexcept ; T * fetch_add ( ptrdiff_t , memory_order = memory_order :: seq_cst ) noexcept ; T * fetch_sub ( ptrdiff_t , memory_order = memory_order :: seq_cst ) volatile noexcept ; T * fetch_sub ( ptrdiff_t , memory_order = memory_order :: seq_cst ) noexcept ; atomic () noexcept = default ; constexpr atomic ( T * ) noexcept ; atomic ( const atomic & ) = delete ; atomic & operator = ( const atomic & ) = delete ; atomic & operator = ( const atomic & ) volatile = delete ; T * operator = ( T * ) volatile noexcept ; T * operator = ( T * ) noexcept ; T * operator ++ ( int ) volatile noexcept ; T * operator ++ ( int ) noexcept ; T * operator -- ( int ) volatile noexcept ; T * operator -- ( int ) noexcept ; T * operator ++ () volatile noexcept ; T * operator ++ () noexcept ; T * operator -- () volatile noexcept ; T * operator -- () noexcept ; T * operator += ( ptrdiff_t ) volatile noexcept ; T * operator += ( ptrdiff_t ) noexcept ; T * operator -= ( ptrdiff_t ) volatile noexcept ; T * operator -= ( ptrdiff_t ) noexcept ; }; } There is a partial specialization of theclass template for pointers. Specializations of this partial specialization are standard-layout structs. They each have a trivial default constructor and a trivial destructor.
atomic
Modify [atomics.flag] as follows:
30.9 Flag type and operations [atomics.flag]namespace std { struct atomic_flag { bool test ( memory_order = memory_order :: seq_cst ) const volatile noexcept ; bool test ( memory_order = memory_order :: seq_cst ) const 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 ;
void wait ( bool , memory_order = memory_order :: seq_cst ) const volatile noexcept ;
void wait ( bool , memory_order = memory_order :: seq_cst ) const noexcept ;
void notify_one () volatile noexcept ;
void notify_one () noexcept ;
void notify_all () volatile noexcept ;
void notify_all () 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 ( const volatile atomic_flag * ) noexcept ; bool atomic_flag_test ( const atomic_flag * ) noexcept ; bool atomic_flag_test_explicit ( const volatile atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_explicit ( const 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 ; void atomic_flag_wait ( const volatile atomic_flag * , bool ) noexcept ; void atomic_flag_wait ( const atomic_flag * , bool ) noexcept ; void atomic_flag_wait_explicit ( const volatile atomic_flag * , bool , memory_order ) noexcept ; void atomic_flag_wait_explicit ( const atomic_flag * , bool , memory_order ) noexcept ; void atomic_flag_notify_one ( volatile atomic_flag * ) noexcept ; void atomic_flag_notify_one ( atomic_flag * ) noexcept ; void atomic_flag_notify_all ( volatile atomic_flag * ) const noexcept ; void atomic_flag_notify_all ( atomic_flag * ) const noexcept ; #
define ATOMIC_FLAG_INIT see below } Thetype provides the classic test-and-set functionality. It has two states, set and clear.
atomic_flag Operations on an object of typeshall be lock-free. [ Note: Hence the operations should also be address-free. — end note ]
atomic_flag Thetype is a standard-layout struct. It has a trivial default constructor and a trivial destructor.
atomic_flag The macroshall 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 ( const volatile atomic_flag * object ) noexcept ; bool atomic_flag_test ( const atomic_flag * object ) noexcept ; bool atomic_flag_test_explicit ( const volatile atomic_flag * object , memory_order order ) noexcept ; bool atomic_flag_test_explicit ( const atomic_flag * object , memory_order order ) noexcept ; bool atomic_flag :: test ( memory_order order = memory_order :: seq_cst ) const volatile noexcept ; bool atomic_flag :: test ( memory_order order = memory_order :: seq_cst ) const noexcept ; Expects:is neither
order nor
memory_order :: release .
memory_order :: acq_rel Effects: Memory is affected according to the value of, or according to
order for
memory_order :: seq_cst .
atomic_flag_test Returns: Atomically returns the value pointed to byor
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 byor 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 ; Expects:
is neither
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 void atomic_flag_wait ( const volatile atomic_flag * object , bool old ) noexcept ; void atomic_flag_wait ( const atomic_flag * object , bool old ) noexcept ; void atomic_flag_wait_explicit ( const volatile atomic_flag * object , bool old , memory_order order ) noexcept ; void atomic_flag_wait_explicit ( const atomic_flag * object , bool old , memory_order order ) noexcept ; void atomic_flag :: wait ( bool old , memory_order order = memory_order :: seq_cst ) const volatile noexcept ; void atomic_flag :: wait ( bool old , memory_order order = memory_order :: seq_cst ) const noexcept ; Expects:is neither
order nor
memory_order :: release .
memory_order :: acq_rel Effects: Letbe
af for the non-member functions and
object for the member functions. Let
this be
mo for
memory_order :: seq_cst and the value of
atomic_flag_wait for the other functions. Repeatedly performs the following steps, in order:
order
Evaluates
.
af -> load ( mo ) != old If the result of that evaluation is
true
, returns.Blocks until it is unblocked by an atomic notifying operation or is unblocked spuriously.
Remarks: This function is an atomic waiting operation.void atomic_flag_notify_one ( volatile atomic_flag * object ) noexcept ; void atomic_flag_notify_one ( atomic_flag * object ) noexcept ; void atomic_flag :: notify_one () volatile noexcept ; void atomic_flag :: notify_one () noexcept ; Effects: Given the set W of atomic waiting operations such that:
the atomic waiting operation blocked after observing the result of some atomic operation
,
X
precedes some atomic operation
X in the modification order of
Y or
* object , and
* this
happens before this call.
Y If the set W is not empty, unblocks the execution of one operation in set W.
Remarks: This function is an atomic notifying operation.void atomic_flag_notify_all ( volatile atomic_flag * object ) const noexcept ; void atomic_flag_notify_all ( atomic_flag * object ) const noexcept ; void atomic_flag :: notify_all () volatile noexcept ; void atomic_flag :: notify_all () noexcept ; Effects: Unblocks the execution of all atomic waiting operations such that:
the atomic waiting operation blocked after observing the result of some atomic operation
,
X
precedes some atomic operation
X in the modification order of
Y or
* object , and
* this
happens before this call.
Y Remarks: This function is an atomic notifying operation.
Modify Table 134 "Thread support library summary" in [thread.general] as follows:
Table 134 — Thread support library summary
Subclause Header(s) 31.2 Requirements 31.3 Threads
< thread > 31.4 Mutual exclusion
< mutex >
< shared_mutex > 31.5 Condition variables
< condition_variable > 31.� Semaphores
< semaphore > 31.� Latches and barriers
< latch >
< barrier > 31.6 Futures
< future >
Add two new subclauses after [thread.condition]:
31.� Semaphores [thread.semaphore]Semaphores are lightweight synchronization primitives used to constrain concurrent access to a shared resource. They are widely used to implement other synchronization primitives and, whenever both are applicable, can be more efficient than condition variables.A counting semaphore is a semaphore object that models a non-negative resource count. A binary semaphore is a semaphore object that has only two states known as available and unavailable. [ Note: A binary semaphore should be more efficient than a counting semaphore with a unit magnitude count. – end note ]
31.�.1 Headersynopsis [thread.semaphore.syn]
< semaphore >
namespace std { template < ptrdiff_t least_max_value = implementation - defined > class counting_semaphore ; using binary_semaphore = counting_semaphore < 1 > ; }
31.�.2 Class template[thread.semaphore.counting.class]
counting_semaphore
namespace std { template < ptrdiff_t least_max_value > class counting_semaphore { public : static constexpr ptrdiff_t max () noexcept ; constexpr explicit counting_semaphore ( ptrdiff_t desired ); ~ counting_semaphore (); counting_semaphore ( const counting_semaphore & ) = delete ; counting_semaphore & operator = ( const counting_semaphore & ) = delete ; void release ( ptrdiff_t update = 1 ); void acquire (); bool try_acquire () noexcept ; template < class Rep , class Period > bool try_acquire_for ( const chrono :: duration < Rep , Period >& rel_time ); template < class Clock , class Duration > bool try_acquire_until ( const chrono :: time_point < Clock , Duration >& abs_time ); private : ptrdiff_t counter ; // exposition only }; } Classmaintains an internal counter that is initialized when the semaphore is created. The counter is decremented when a thread acquires the semaphore, and is incremented when a thread releases the semaphore. If a thread tries to acquire the semaphore when the counter is zero, the thread will block until another thread increments the counter by releasing the semaphore.
counting_semaphore shall be greater than zero.
least_max_value s permit concurrent invocation of the
counting_semaphore ,
release ,
acquire ,
try_acquire , and
try_acquire_for member functions.
try_acquire_until static constexpr ptrdiff_t max () noexcept ; Returns: The maximum value of. This value is greater than or equal to
counter .
least_max_value constexpr explicit counting_semaphore ( ptrdiff_t desired ); Expects:is greater than or equal to zero and less than or equal to
desired .
max () Effects: Initializeswith
counter .
desired Throws: Nothing.~ counting_semaphore (); Expects: For every function call blocked on, a function call that will cause it to unblock and return has happened before this call. [ Note: This relaxes the usual rules, which would have required all blocking function calls to happen before destruction. — end note ]
* this void release ( ptrdiff_t update = 1 ); Expects:is greater than or equal to zero, and
update is less than or equal to
counter + update .
max () Effects: Atomically execute. Then, unblock any threads that are waiting for
counter += update to be greater than zero.
counter Synchronization: Strongly happens before invocations ofthat observe the result of the effects.
try_acquire Throws:when an exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).bool try_acquire () noexcept ; Effects:
With low probability, returns immediately. [ Note: An implementation should ensure that
does not consistently return
try_acquire false
in the absence of contending acquisitions. — end note ]Otherwise, if
is greater than zero, atomically decrement
counter by one.
counter Returns:true
ifwas decremented, otherwise
counter false
.void acquire (); Effects: Repeatedly performs the following steps, in order:
Evaluates
. If the result is
try_acquire true
, returns.Blocks on
until
* this is greater than zero.
counter Throws:when an exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).template < class Rep , class Period > bool try_acquire_for ( const chrono :: duration < Rep , Period >& rel_time ); template < class Clock , class Duration > bool try_acquire_until ( const chrono :: time_point < Clock , Duration >& abs_time ); Effects: Repeatedly performs the following steps, in order:
Evaluates
. If the result is
try_acquire true
, returnstrue
.Blocks on
until
* this is greater than zero or until the timeout expires. If it is unblocked by the timeout expiring, returns
counter false
.The timeout expires when the current time is after
(for
abs_time ) or when at least
try_acquire_until has passed from the start of the function (for
rel_time ).
try_acquire_for Throws: Timeout-related exceptions (31.2.4), orwhen a non-timeout-related exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).
31.� Coordination Types [thread.coord]This subclause describes various concepts related to thread coordination, and defines the coordination typesand
latch . These types facilitate concurrent computation performed by a number of threads. Concurrent invocations of the member functions of
barrier and
latch , other than their destructors, do not introduce data races.
barrier
31.�.1 Latches [thread.coord.latch]A latch is a thread coordination mechanism that allows any number of threads to block until the latch is arrived at (via thefunction) an expected number of times. The expected count is set when the latch is created. An individual latch is a single-use object; once the expected count has been reached, the latch cannot be reused.
count_down
31.�.1.1 Headersynopsis [thread.coord.latch.syn]
< latch > namespace std { class latch ; }
31.�.1.2 Class[thread.coord.latch.class]
latch
namespace std { class latch { public : constexpr explicit latch ( ptrdiff_t expected ); ~ latch (); latch ( const latch & ) = delete ; latch & operator = ( const latch & ) = delete ; void count_down ( ptrdiff_t update = 1 ); bool try_wait () const noexcept ; void wait () const ; void arrive_and_wait ( ptrdiff_t update = 1 ); private : ptrdiff_t counter ; // exposition only }; } Amaintains an internal counter that is initialized when the
latch is created. Threads can block on the latch object, waiting for
latch to be decremented to zero.
counter constexpr explicit latch ( ptrdiff_t expected ); Expects:is greater than or equal to zero.
expected Effects: Initializeswith
counter .
expected Throws: Nothing.~ latch (); Expects: No threads are blocked on. [ Note: May be called even if some threads have not yet returned from invocations of
* this on this object, provided that they are unblocked. This relaxes the usual rules, which would have required all blocking function calls to happen before destruction. - end note ]
wait Remarks: The destructor may block until all threads have exited invocations ofon this object.
wait void count_down ( ptrdiff_t update = 1 ); Expects:is greater than or equal to zero, and
update is less than or equal to
update .
counter Effects: Atomically decrementsby
counter . If
update is equal to zero, unblocks all threads blocked on
counter .
* this Synchronization: Synchronizes with the returns from all calls that are unblocked.Throws:when an exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).bool try_wait () const noexcept ; Returns:.
counter == 0 void wait () const ; Effects: Ifequals zero, returns immediately. Otherwise, blocks on
counter until it is unblocked by a call to
* this that decrements
count_down to zero.
counter Throws:when an exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).void arrive_and_wait ( ptrdiff_t update = 1 ); Effects: Equivalent to:
count_down ( update );
wait (); Throws:when an exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).
31.�.2 Barriers [thread.coord.barrier]A barrier is a thread coordination mechanism whose lifetime consists of a sequence of phases, where each phase allows a certain number of threads to arrive at the barrier and then wait for all the other threads to also arrive at the barrier. [ Note: A barrier is useful for managing repeated tasks that are handled by multiple threads. - end note ]
31.�.2.1 Headersynopsis [thread.coord.barrier.syn]
< barrier >
namespace std { template < class CompletionFunction = unspecified > class barrier ; }
31.�.2.2 Class template[thread.coord.barrier.class]
barrier
namespace std { template < class CompletionFunction > class barrier { public : using arrival_token = see below ; constexpr explicit barrier ( ptrdiff_t expected , CompletionFunction f = {}); ~ barrier (); barrier ( const barrier & ) = delete ; barrier & operator = ( const barrier & ) = delete ; [[ nodiscard ]] arrival_token arrive ( ptrdiff_t update = 1 ); void wait ( arrival_token && arrival ) const ; void arrive_and_wait (); void arrive_and_drop (); private : CompletionFunction completion ; // exposition only }; } Each phase of the barrier consists of the following steps:
The expected count is reset to what was specified by the
argument to the constructor, possibly adjusted by calls to
expected .
arrive_and_drop The expected count is decremented by each call to
.
arrive When the expected count reaches zero, the completion step is run on one of the threads that arrived at the barrier during the phase.
When the completion step finishes, the next phase starts.
Each phase defines a synchronization point. Threads that arrive at the barrier during the phase can block on the phase’s synchronization point by calling, and will remain blocked until the phase completes.
wait The completion step that is executed at the end of each phase has the following effects:
Invokes the completion function, equivalent to
.
completion () Unblocks all threads that are blocked on the phase’s synchronization point.
The end of the completion step strongly happens before the returns from all calls that were unblocked by the completion step.
shall meet the Cpp17MoveConstructible (Table 26) requirements and Cpp17Destructable (Table 30) requirements.
CompletionFunction shall be
is_invocable_r_v < void , CompletionFunction > true
, andshall be
noexcept ( declval < CompletionFunction > ()()) true
.is an unspecified type, such that
barrier :: arrival_token is
is_nothrow_move_constructible_v < barrier :: arrival_token > true
andis
is_nothrow_move_assignable_v < barrier :: arrival_token > true
.constexpr explicit barrier ( ptrdiff_t expected , CompletionFunction f = {}); Expects:is greater than or equal to zero.
expected Effects: Sets the initial expected count for each phase to. Initializes
expected with
completion . Starts the first phase. [ Note: If
std :: move ( f ) is
expected this object can only be destroyed. — end note ]
0 Throws: Any exception thrown by's move constructor.
CompletionFunction ~ barrier (); Expects: No threads are blocked at a synchronization point for any phase of this object. [ Note: May be called even if some threads have not yet returned from invocations of, provided that they have unblocked. This relaxes the usual rules, which would have required all blocking function calls to happen before destruction. - end note ]
wait Remarks: The destructor may block until all threads have exited invocations ofon this object.
wait [[ nodiscard ]] arrival_token arrive ( ptrdiff_t update = 1 ); Expected:is greater than zero, and
update is less than or equal to the expected count.
update Effects: Constructs an object of typethat is associated with the
arrival_token 's synchronization point for the current phase. Then, decrements the expected count by
barrier .
update Synchronization: The call tostrongly happens before the start of the completion step for the current phase.
arrive Returns: The constructedobject.
arrival_token Throws:when an exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).Remarks: This call can cause the completion step for the current phase to start.void wait ( arrival_token && arrival ) const ; Expects:is associated with the synchronization point for the current or the immediately preceding phase of the same barrier object.
arrival Effects: Blocks at the synchronization point associated withuntil the completion step of the synchronization point’s phase is run. [ Note: If
std :: move ( arrival ) is associated with the synchronization point for a previous phase, the call returns immediately. - end note ]
arrival Throws:when an exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).void arrive_and_wait (); Expects: The expected count for the current phase is greater than zero.Effects: Equivalent to.
wait ( arrive ()) Throws:when an exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).Remarks: This call can cause the completion step for the current phase to start.void arrive_and_drop (); Expects: The expected count for the current phase is greater than zero.Effects: Decrements the initial expected count for all subsequent phases by one. Then decrements the expected count for the current phase by one.Synchronization: The call tostrongly happens before the start of the completion step for the current phase.
arrive_and_drop Throws:when an exception is required (31.2.2).
system_error Error conditions: Any of the error conditions allowed for mutex types (31.4.3.2).Remarks: This call can cause the completion step for the current phase to start.
Create the following feature test macros with the given headers, adding them to the table in [support.limits.general]:
-
in__cpp_lib_atomic_lock_free_type_aliases
, which implies that< atomic >
andatomic_signed_lock_free
types are available.atomic_unsigned_lock_free -
in__cpp_lib_atomic_flag_test
, which implies the< atomic >
methods and free functions fortest
are available.atomic_flag -
in__cpp_lib_atomic_wait
, which implies the< atomic >
andnotify_ *
methods and free functions forwait
andatomic
are available.atomic_flag -
in__cpp_lib_semaphore
, which implies that< semaphore >
andcounting_semaphore
are available.binary_semaphore -
in__cpp_lib_latch
, which implies that< latch >
is available.latch -
in__cpp_lib_barrier
, which implies that< barrier >
is available.barrier