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
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 >* ); void atomic_notify_one ( volatile atomic_flag * ); void atomic_notify_one ( atomic_flag * ); template < class T > void atomic_notify_all ( volatile atomic < T >* ); template < class T > void atomic_notify_all ( atomic < T >* ); void atomic_notify_all ( volatile atomic_flag * ); void atomic_notify_all ( atomic_flag * ); // 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 ( 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 ; 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 , andatomic_intptr_t are defined if and only ifatomic_uintptr_t ,intN_t ,uintN_t , andintptr_t are defined, respectively.uintptr_t The type aliasesandatomic_signed_lock_free are defined to be specializations ofatomic_unsigned_lock_free whose template arguments are integral types, respectively signed and unsigned, other thanatomic . In freestanding implementations (4.1), these aliases are optional. If an implementation provides a integral specialization ofbool other thanatomic for whichbool is true, it shall defineis_always_lock_free andatomic_signed_lock_free . Otherwise, they shall not be defined.atomic_unsigned_lock_free shall beis_always_lock_free trueforandatomic_signed_lock_free . An implementation which defines these type aliases should choose the integral specialization ofatomic_unsigned_lock_free for which the atomic waiting and notifying operations are most efficient.atomic
Note: The reference to "atomic waiting and notifying operations" in the above change should refer to the new [atomic.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
andatomic_wait .atomic_wait_explicit The following functions are atomic notifying operations:
andatomic < T >:: notify_one .atomic < T >:: notify_all
andatomic_flag :: notify_one .atomic_flag :: notify_all
andatomic_notify_one .atomic_notify_one_explicit
andatomic_flag_notify_one .atomic_flag_notify_one_explicit
andatomic_notify_all .atomic_notify_all_explicit
andatomic_flag_notify_all .atomic_flag_notify_all_explicit Atomic waiting operations in this facility 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 old , memory_order = memory_order :: seq_cst ) const volatile noexcept ; void wait ( T old , 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 ; Requires: Theargument shall not beorder normemory_order_release .memory_order_acq_rel Effects: Repeatedly performs the following steps, in order:
Evaluates
then, if the result isobject -> load ( order ) != old true, returns.Blocks until an implementation-defined condition has been met. [ Note: Consequently, it may unblock for reasons other than an atomic notifying operation. — end note ]
Remarks: This function is an atomic waiting operation.void notify_one () volatile noexcept ; void notify_one () noexcept ; Effects: Unblocks up to execution of an atomic waiting operation that blocked after observing the result of an atomic operation, if there exists another atomic operationX , such thatY precedesX in the modification order ofY , and* this happens before this call.Y Remarks: This function is an atomic notifying operation.void notify_all () volatile noexcept ; void notify_all () noexcept ; Effects: Unblocks each execution of an atomic waiting operation that blocked after observing the result of an atomic operation, if there exists another atomic operationX , such thatY precedesX in the modification order ofY , 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 typesatomic ,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 headerwchar_t . For each such type< cstdint > , the specializationintegral provides additional atomic operations appropriate to integral types. [ Note: For the specializationatomic < 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 old , memory_order = memory_order :: seq_cst ) const volatile noexcept ; void wait ( integral old , 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 typesatomic ,float , anddouble . For each such typelong double , the specializationfloating - 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 old , memory_order = memory_order :: seq_cst ) const volatile noexcept ; void wait ( floating - point old , 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 * old , memory_order = memory_order :: seq_cst ) const volatile noexcept ; void wait ( T * old , 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 ) 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 ; 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 ( 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 ; 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 typeATOMIC_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 anATOMIC_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: Theargument shall not beorder normemory_order_release .memory_order_acq_rel Effects: Memory is affected according to the value of.order Returns: Atomically returns the value pointed to byorobject .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 byobject tothis 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 beorder ,memory_order_consume , normemory_order_acquire .memory_order_acq_rel Effects: Atomically sets the value pointed to by
or byobject tothis 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 ; Requires: Theargument shall not beorder normemory_order_release .memory_order_acq_rel Effects: Repeatedly performs the following steps, in order:
Evaluates
then, if the result isobject -> load ( order ) != old true, returns.Blocks until an implementation-defined condition has been met. [ Note: Consequently, it may unblock for reasons other than an atomic notifying operation. — end note ]
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: Unblocks up to one execution of a atomic waiting operation that blocked after observing the result of an atomic operation, if there exists another atomic operationX , such thatY precedesX in the modification order ofY or* object , and* this happens before this call.Y 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 each execution of a atomic waiting operation that blocked after observing the result of an atomic operation, if there exists another atomic operationX , such thatY precedesX in the modification order ofY 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, also 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 basic_semaphore & ) = delete ; counting_semaphore ( basic_semaphore && ) = delete ; counting_semaphore & operator = ( const basic_semaphore & ) = delete ; counting_semaphore & operator = ( basic_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. Threads may block waiting untilcounting_semaphore .counter >= 1 Semaphores permit concurrent invocation of the,release ,acquire ,try_acquire , andtry_acquire_for member functions.try_acquire_until static constexpr ptrdiff_t max () noexcept ; Returns: The maximum value of. This value shall not be less than that of the template argumentcounter . [ Note: The value may exceed least_max_value. – end note ]least_max_value constexpr explicit counting_semaphore ( ptrdiff_t desired ); Requires:anddesired >= 0 .desired <= max () Effects:.counter = desired Throws: Nothing.~ counting_semaphore (); Requires: For every function call that blocks on, a function call that will cause it to unblock and return shall happen before this call. [ Note: This relaxes the usual rules, which would have required all wait calls to happen before destruction. — end note ]counter Effects: Destroys the object.Throws: Nothing.void release ( ptrdiff_t update = 1 ); Requires:, andupdate >= 0 .counter + update <= max () Effects:, executed atomically. If any threads are blocked on counter, unblocks them.counter += update 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 bool try_acquire () noexcept ; Effects:
With low probability, returns immediately. [ Note: An implementation should ensure that
does not consistently returntry_acquire falsein the absence of contending acquisitions. — end note ]Otherwise, if
, thencounter >= 1 is executed atomically.counter -= 1 Returns:trueifwas decremented, otherwisecounter false.void acquire (); Effects: Repeatedly performs the following steps, in order:
Evaluates
, then, if the result istry_acquire true, returns.Blocks until
.counter >= 1 Throws:when an exception is required (31.2.2).system_error 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 istry_acquire true, returnstrue.Blocks until the timeout expires or
. If the timeout expired, returnscounter >= 1 false.Throws: Timeout-related exceptions (31.2.4).
31.� Coordination Types [thread.coord]This section describes various concepts related to thread coordination, and defines the coordination typesandlatch . These types facilitate concurrent computation performed by a number of threads, in one or more phases.barrier In this subclause, a synchronization point represents a condition that a thread may contribute to or wait for, potentially blocking until it is satisfied. A thread arrives at the synchronization point when it has an effect on the state of the condition, even if it does not cause it to become satisfied.Concurrent invocations of the member functions of coordination types, other than their destructors, do not introduce data races.
31.�.1 Latches [thread.coord.latch]A latch is a thread coordination mechanism that allows any number of threads to block until an expected count is summed (exactly) by threads that arrived at the latch. The expected count is set when the latch is constructed. An individual latch is a single-use object; once the count has been reached, the latch cannot be reused.
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 ( latch && ) = delete ; latch & operator = ( const latch & ) = delete ; latch & operator = ( 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 thelatch is created. Threads may block at thelatch ’s synchronization point, waiting forlatch to be decremented tocounter .0 constexpr explicit latch ( ptrdiff_t expected ); Requires:.expected >= 0 Effects:.counter = expected Throws: Nothing.~ latch (); Requires: No threads are blocked at the synchronization point.Effects: Destroys the latch.Throws: Nothing.Remarks: May be called even if some threads have not yet returned from functions that block at the synchronization point, provided that they are unblocked. [ Note: The destructor may block until all threads have exited invocations ofon this object. — end note ]wait void count_down ( ptrdiff_t update = 1 ); Requires:andcounter >= update .update >= 0 Effects: Atomically decrementsbycounter .update Synchronization: Synchronizes with the returns from all calls unblocked by the effects.Throws:when an exception is required (31.2.2).system_error Remarks: Arrives at the synchronization point withcount.update bool try_wait () const noexcept ; Returns:.counter == 0 void wait () const noexcept ; Effects: If, returns immediately. Otherwise, blocks the calling thread at the synchronization point untilcounter == 0 .counter == 0 Throws:when an exception is required (31.2.2).system_error 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
31.�.2 Barriers [thread.coord.barrier]A barrier is a thread coordination mechanism that allows at most an expected count of threads to block until that count is summed (exactly) by threads that arrived at the barrier in each of its successive phases. Once threads are released from blocking at the synchronization point for a phase, they can reuse the same barrier immediately in its next phase. [ Note: It is thus useful for managing repeated tasks, or phases of a larger task, that are handled by multiple threads. — end note ]A barrier has a completion step that is a (possibly empty) set of effects associated with a phase of the barrier. When the member functions defined in this subclause arrive at the barrier, they have the following effects:
When the expected number of threads for this phase have arrived at the barrier, one of those threads executes the barrier type’s completion step.
When the completion step is completed, all threads blocked at the synchronization point for this phase are unblocked and the barrier enters its next phase. The end of the completion step strongly happens before the returns from all calls unblocked by its completion.
31.�.2.1 Headersynopsis [thread.coord.barrier.syn]< barrier > namespace std { template < class CompletionFunction = implementation - defined > class barrier ; }
31.�.2.2 Class template[thread.coord.barrier.class]barrier namespace std { template < class CompletionFunction > class barrier { public : using arrival_token = implementation - defined ; constexpr explicit barrier ( ptrdiff_t expected , CompletionFunction f = CompletionFunction ()); ~ barrier (); barrier ( const barrier & ) = delete ; barrier ( barrier && ) = delete ; barrier & operator = ( const barrier & ) = delete ; barrier & operator = ( 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 }; } Ais a barrier type with a completion step controlled by a function object. The completion step callsbarrier . Threads may block at the barrier’s synchronization point for a phase, waiting for the expected sum contributions by threads that arrive in that phase.completion shall beCompletionFunction (Table 26),Cpp17MoveConstructible shall beis_invocable_r_v < void , CompletionFunction > true, andshall benoexcept ( declval < CompletionFunction > ()()) true.is an implementation-defined type.barrier :: arrival_token shall beis_nothrow_move_constructible_v < barrier :: arrival_token > trueandshall beis_nothrow_move_assignable_v < barrier :: arrival_token > true.constexpr explicit barrier ( ptrdiff_t expected , CompletionFunction f = CompletionFunction ()); Requires:, andexpected >= 0 shall benoexcept ( f ()) true.Effects: Initializes the barrier fornumber of threads in the first phase, and initializesexpected withcompletion . [ Note: Ifmove ( f ) isexpected this object may only be destroyed. — end note ]0 Throws: Any exception thrown by's move constructor.CompletionFunction ~ barrier (); Requires: No threads are blocked at a synchronization point for any phase.Effects: Destroys the barrier.Remarks: May be called even if some threads have not yet returned from functions that block at a synchronization point, provided that they have unblocked. [ Note: The destructor may block until all threads have exited invocations of wait() on this object. — end note ][[ nodiscard ]] arrival_token arrive ( ptrdiff_t update = 1 ); Requires: The expected count is not less than.update Effects: Constructs an object of typethat is associated with thearrival_token 's synchronization point for the current phase, then arrivesbarrier times at the synchronization point for the current phase.update Synchronization: The call tostrongly happens before the start of the completion step for the current phase.arrive Returns: The constructed object.Throws:when an exception is required (31.2.2).system_error Remarks: This may cause the completion step to start.void wait ( arrival_token && arrival ) const ; Requires:is associated with a synchronization point for the current or the immediately preceding phases of thearrival .barrier Effects: Blocks at the synchronization point associated withuntil the condition is satisfied.std :: move ( arrival ) Throws:when an exception is required (31.2.2).system_error void arrive_and_wait (); Effects: Equivalent to.wait ( arrive ()) Throws:when an exception is required (31.2.2).system_error void arrive_and_drop (); Requires: The expected number of threads for the current phase is not.0 Effects: Decrements the expected number of threads for subsequent phases by, then arrives at the synchronization point for the current phase.1 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 Remarks: This may cause the completion step to start.
Create the following feature test macros:
-
, which implies that__cpp_lib_atomic_lock_free_type_aliases andatomic_signed_lock_free types are available.atomic_unsigned_lock_free -
, which implies the__cpp_lib_atomic_flag_test methods and free functions fortest are available.atomic_flag -
, which implies the__cpp_lib_atomic_wait andnotify_ * methods and free functions forwait andatomic are available.atomic_flag -
, which implies that__cpp_lib_semaphore andcounting_semaphore are available.binary_semaphore -
, which implies that__cpp_lib_latch is available.latch -
, which implies that__cpp_lib_barrier is available.barrier