зеркало из https://github.com/microsoft/STL.git
Родитель
b90b3e05d2
Коммит
3b2956b18e
|
@ -8,6 +8,7 @@ set(HEADERS
|
|||
${CMAKE_CURRENT_LIST_DIR}/inc/any
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/array
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/atomic
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/barrier
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/bit
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/bitset
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/cassert
|
||||
|
@ -153,6 +154,7 @@ set(HEADERS
|
|||
${CMAKE_CURRENT_LIST_DIR}/inc/iso646.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/istream
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/iterator
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/latch
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/limits
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/list
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/locale
|
||||
|
@ -171,6 +173,7 @@ set(HEADERS
|
|||
${CMAKE_CURRENT_LIST_DIR}/inc/ratio
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/regex
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/scoped_allocator
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/semaphore
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/set
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/shared_mutex
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc/span
|
||||
|
|
|
@ -105,6 +105,9 @@
|
|||
|
||||
#ifndef _M_CEE_PURE
|
||||
#include <atomic>
|
||||
#include <barrier>
|
||||
#include <latch>
|
||||
#include <semaphore>
|
||||
#endif // _M_CEE_PURE
|
||||
|
||||
#ifndef _M_CEE
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
// barrier standard header
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#pragma once
|
||||
#ifndef _BARRIER_
|
||||
#define _BARRIER_
|
||||
#include <yvals.h>
|
||||
#if _STL_COMPILER_PREPROCESSOR
|
||||
|
||||
#ifdef _M_CEE_PURE
|
||||
#error <barrier> is not supported when compiling with /clr:pure.
|
||||
#endif // _M_CEE_PURE
|
||||
|
||||
#if !_HAS_CXX20
|
||||
#pragma message("The contents of <barrier> are available only with C++20 or later.")
|
||||
#else // ^^^ !_HAS_CXX20 / _HAS_CXX20 vvv
|
||||
|
||||
#include <atomic>
|
||||
#include <limits.h>
|
||||
#include <type_traits>
|
||||
#include <xmemory>
|
||||
|
||||
#pragma pack(push, _CRT_PACKING)
|
||||
#pragma warning(push, _STL_WARNING_LEVEL)
|
||||
#pragma warning(disable : _STL_DISABLED_WARNINGS)
|
||||
_STL_DISABLE_CLANG_WARNINGS
|
||||
#pragma push_macro("new")
|
||||
#undef new
|
||||
|
||||
_STD_BEGIN
|
||||
|
||||
struct _No_completion_function {
|
||||
void operator()() noexcept {}
|
||||
};
|
||||
|
||||
template <class _Completion_function = _No_completion_function>
|
||||
class barrier;
|
||||
|
||||
inline constexpr ptrdiff_t _Barrier_arrival_token_mask = 1;
|
||||
inline constexpr ptrdiff_t _Barrier_value_mask = ~_Barrier_arrival_token_mask;
|
||||
inline constexpr ptrdiff_t _Barrier_value_shift = 1;
|
||||
inline constexpr ptrdiff_t _Barrier_invalid_token = 0;
|
||||
inline constexpr ptrdiff_t _Barrier_value_step = 1 << _Barrier_value_shift;
|
||||
inline constexpr ptrdiff_t _Barrier_max = (1ULL << (sizeof(ptrdiff_t) * CHAR_BIT - 2)) - 1;
|
||||
|
||||
template <class _Completion_function>
|
||||
class _Arrival_token {
|
||||
public:
|
||||
_Arrival_token(_Arrival_token&& _Other) noexcept {
|
||||
_Value = _Other._Value;
|
||||
_Other._Value = _Barrier_invalid_token;
|
||||
}
|
||||
|
||||
_Arrival_token& operator=(_Arrival_token&& _Other) noexcept {
|
||||
_Value = _Other._Value;
|
||||
_Other._Value = _Barrier_invalid_token;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit _Arrival_token(ptrdiff_t _Value_) noexcept : _Value(_Value_) {}
|
||||
friend barrier<_Completion_function>;
|
||||
|
||||
ptrdiff_t _Value;
|
||||
};
|
||||
|
||||
template <class _Completion_function>
|
||||
class barrier {
|
||||
public:
|
||||
static_assert(
|
||||
#ifndef __cpp_noexcept_function_type
|
||||
is_function_v<remove_pointer_t<_Completion_function>> ||
|
||||
#endif // __cpp_noexcept_function_type
|
||||
is_nothrow_invocable_v<_Completion_function&>,
|
||||
"N4861 [thread.barrier.class]/5: is_nothrow_invocable_v<CompletionFunction&> shall be true");
|
||||
|
||||
using arrival_token = _Arrival_token<_Completion_function>;
|
||||
|
||||
constexpr explicit barrier(
|
||||
const ptrdiff_t _Expected, _Completion_function _Fn = _Completion_function()) noexcept /* strengthened */
|
||||
: _Val(_One_then_variadic_args_t{}, _STD move(_Fn), _Expected << _Barrier_value_shift) {
|
||||
_STL_VERIFY(_Val._Myval2._Current.load(memory_order_relaxed) >= 0,
|
||||
"Precondition: expected >= 0 and expected <= max() (N4861 [thread.barrier.class]/9)");
|
||||
}
|
||||
|
||||
barrier(const barrier&) = delete;
|
||||
barrier& operator=(const barrier&) = delete;
|
||||
|
||||
_NODISCARD static constexpr ptrdiff_t(max)() noexcept {
|
||||
return _Barrier_max;
|
||||
}
|
||||
|
||||
_NODISCARD arrival_token arrive(ptrdiff_t _Update = 1) noexcept /* strengthened */ {
|
||||
// Shifting before precondition check, so that exceeding max() will trigger precondition check too
|
||||
_Update <<= _Barrier_value_shift;
|
||||
_STL_VERIFY(_Update > 0, "Precondition: update > 0 (N4861 [thread.barrier.class]/12)");
|
||||
// TRANSITION, GH-1133: should be memory_order_release
|
||||
ptrdiff_t _Current = _Val._Myval2._Current.fetch_sub(_Update) - _Update;
|
||||
_STL_VERIFY(_Current >= 0, "Precondition: update is less than or equal to the expected count "
|
||||
"for the current barrier phase (N4861 [thread.barrier.class]/12)");
|
||||
if ((_Current & _Barrier_value_mask) == 0) {
|
||||
// TRANSITION, GH-1133: should have this fence:
|
||||
// atomic_thread_fence(memory_order_acquire);
|
||||
_Completion(_Current);
|
||||
}
|
||||
// Embedding this into the token to provide an additional correctness check that the token is from the same
|
||||
// barrier and wasn't used. All bits of this fit, as barrier should be aligned to at least the size of an
|
||||
// atomic counter.
|
||||
return arrival_token{(_Current & _Barrier_arrival_token_mask) | reinterpret_cast<intptr_t>(this)};
|
||||
}
|
||||
|
||||
void wait(arrival_token&& _Arrival) const noexcept /* strengthened */ {
|
||||
_STL_VERIFY((_Arrival._Value & _Barrier_value_mask) == reinterpret_cast<intptr_t>(this),
|
||||
"Preconditions: arrival is associated with the phase synchronization point for the current phase "
|
||||
"or the immediately preceding phase of the same barrier object (N4861 [thread.barrier.class]/19)");
|
||||
const ptrdiff_t _Arrival_value = _Arrival._Value & _Barrier_arrival_token_mask;
|
||||
_Arrival._Value = _Barrier_invalid_token;
|
||||
for (;;) {
|
||||
// TRANSITION, GH-1133: should be memory_order_acquire
|
||||
const ptrdiff_t _Current = _Val._Myval2._Current.load();
|
||||
_STL_VERIFY(_Current >= 0, "Invariant counter >= 0, possibly caused by preconditions violation "
|
||||
"(N4861 [thread.barrier.class]/12)");
|
||||
if ((_Current & _Barrier_arrival_token_mask) != _Arrival_value) {
|
||||
break;
|
||||
}
|
||||
_Val._Myval2._Current.wait(_Current, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void arrive_and_wait() noexcept /* strengthened */ {
|
||||
// TRANSITION, GH-1133: should be memory_order_acq_rel
|
||||
ptrdiff_t _Current = _Val._Myval2._Current.fetch_sub(_Barrier_value_step) - _Barrier_value_step;
|
||||
const ptrdiff_t _Arrival = _Current & _Barrier_arrival_token_mask;
|
||||
_STL_VERIFY(_Current >= 0, "Precondition: update is less than or equal to the expected count "
|
||||
"for the current barrier phase (N4861 [thread.barrier.class]/12)");
|
||||
if ((_Current & _Barrier_value_mask) == 0) {
|
||||
_Completion(_Current);
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
_Val._Myval2._Current.wait(_Current, memory_order_relaxed);
|
||||
// TRANSITION, GH-1133: should be memory_order_acquire
|
||||
_Current = _Val._Myval2._Current.load();
|
||||
_STL_VERIFY(_Current >= 0, "Invariant counter >= 0, possibly caused by preconditions violation "
|
||||
"(N4861 [thread.barrier.class]/12)");
|
||||
if ((_Current & _Barrier_arrival_token_mask) != _Arrival) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void arrive_and_drop() noexcept /* strengthened */ {
|
||||
const ptrdiff_t _Rem_count =
|
||||
_Val._Myval2._Total.fetch_sub(_Barrier_value_step, memory_order_relaxed) - _Barrier_value_step;
|
||||
_STL_VERIFY(_Rem_count >= 0, "Precondition: The expected count for the current barrier phase "
|
||||
"is greater than zero (N4861 [thread.barrier.class]/24) "
|
||||
"(checked initial expected count, which is not less than the current)");
|
||||
(void) arrive(1);
|
||||
}
|
||||
|
||||
private:
|
||||
void _Completion(const ptrdiff_t _Current) noexcept {
|
||||
const ptrdiff_t _Rem_count = _Val._Myval2._Total.load(memory_order_relaxed);
|
||||
_STL_VERIFY(_Rem_count >= 0, "Invariant: initial expected count less than zero, "
|
||||
"possibly caused by preconditions violation "
|
||||
"(N4861 [thread.barrier.class]/24)");
|
||||
_Val._Get_first()();
|
||||
const ptrdiff_t _New_phase_count = _Rem_count | ((_Current + 1) & _Barrier_arrival_token_mask);
|
||||
// TRANSITION, GH-1133: should be memory_order_release
|
||||
_Val._Myval2._Current.store(_New_phase_count);
|
||||
_Val._Myval2._Current.notify_all();
|
||||
}
|
||||
|
||||
struct _Counter_t {
|
||||
constexpr explicit _Counter_t(ptrdiff_t _Initial) : _Current(_Initial), _Total(_Initial) {}
|
||||
// wait(arrival_token&&) accepts a token from the current phase or the immediately preceding phase; this means
|
||||
// we can track which phase is the current phase using 1 bit which alternates between each phase. For this
|
||||
// purpose we use the low order bit of _Current.
|
||||
atomic<ptrdiff_t> _Current;
|
||||
atomic<ptrdiff_t> _Total;
|
||||
};
|
||||
|
||||
_Compressed_pair<_Completion_function, _Counter_t> _Val;
|
||||
};
|
||||
|
||||
_STD_END
|
||||
|
||||
#pragma pop_macro("new")
|
||||
_STL_RESTORE_CLANG_WARNINGS
|
||||
#pragma warning(pop)
|
||||
#pragma pack(pop)
|
||||
#endif // ^^^ _HAS_CXX20 ^^^
|
||||
|
||||
#endif // _STL_COMPILER_PREPROCESSOR
|
||||
#endif // _BARRIER_
|
|
@ -0,0 +1,101 @@
|
|||
// latch standard header
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#pragma once
|
||||
#ifndef _LATCH_
|
||||
#define _LATCH_
|
||||
#include <yvals.h>
|
||||
#if _STL_COMPILER_PREPROCESSOR
|
||||
|
||||
#ifdef _M_CEE_PURE
|
||||
#error <latch> is not supported when compiling with /clr:pure.
|
||||
#endif // _M_CEE_PURE
|
||||
|
||||
#if !_HAS_CXX20
|
||||
#pragma message("The contents of <latch> are available only with C++20 or later.")
|
||||
#else // ^^^ !_HAS_CXX20 / _HAS_CXX20 vvv
|
||||
|
||||
#include <atomic>
|
||||
#include <limits.h>
|
||||
|
||||
#pragma pack(push, _CRT_PACKING)
|
||||
#pragma warning(push, _STL_WARNING_LEVEL)
|
||||
#pragma warning(disable : _STL_DISABLED_WARNINGS)
|
||||
_STL_DISABLE_CLANG_WARNINGS
|
||||
#pragma push_macro("new")
|
||||
#undef new
|
||||
|
||||
_STD_BEGIN
|
||||
|
||||
class latch {
|
||||
public:
|
||||
_NODISCARD static constexpr ptrdiff_t(max)() noexcept {
|
||||
return (1ULL << (sizeof(ptrdiff_t) * CHAR_BIT - 1)) - 1;
|
||||
}
|
||||
|
||||
constexpr explicit latch(const ptrdiff_t _Expected) noexcept /* strengthened */ : _Counter{_Expected} {
|
||||
_STL_VERIFY(_Expected >= 0, "Precondition: expected >= 0 (N4861 [thread.latch.class]/4)");
|
||||
}
|
||||
|
||||
latch(const latch&) = delete;
|
||||
latch& operator=(const latch&) = delete;
|
||||
|
||||
void count_down(const ptrdiff_t _Update = 1) noexcept /* strengthened */ {
|
||||
_STL_VERIFY(_Update >= 0, "Precondition: update >= 0 (N4861 [thread.latch.class]/7)");
|
||||
// TRANSITION, GH-1133: should be memory_order_release
|
||||
const ptrdiff_t _Current = _Counter.fetch_sub(_Update) - _Update;
|
||||
if (_Current == 0) {
|
||||
_Counter.notify_all();
|
||||
} else {
|
||||
_STL_VERIFY(_Current >= 0, "Precondition: update <= counter (N4861 [thread.latch.class]/7)");
|
||||
}
|
||||
}
|
||||
|
||||
_NODISCARD bool try_wait() const noexcept {
|
||||
// TRANSITION, GH-1133: should be memory_order_acquire
|
||||
return _Counter.load() == 0;
|
||||
}
|
||||
|
||||
void wait() const noexcept /* strengthened */ {
|
||||
for (;;) {
|
||||
// TRANSITION, GH-1133: should be memory_order_acquire
|
||||
const ptrdiff_t _Current = _Counter.load();
|
||||
if (_Current == 0) {
|
||||
return;
|
||||
} else {
|
||||
_STL_VERIFY(_Current > 0, "Invariant counter >= 0, possibly caused by preconditions violation "
|
||||
"(N4861 [thread.latch.class]/7)");
|
||||
}
|
||||
_Counter.wait(_Current, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void arrive_and_wait(const ptrdiff_t _Update = 1) noexcept /* strengthened */ {
|
||||
_STL_VERIFY(_Update >= 0, "Precondition: update >= 0 (N4861 [thread.latch.class]/7)");
|
||||
// TRANSITION, GH-1133: should be memory_order_acq_rel
|
||||
const ptrdiff_t _Current = _Counter.fetch_sub(_Update) - _Update;
|
||||
if (_Current == 0) {
|
||||
_Counter.notify_all();
|
||||
} else {
|
||||
_STL_VERIFY(_Current > 0, "Precondition: update <= counter (N4861 [thread.latch.class]/7)");
|
||||
_Counter.wait(_Current, memory_order_relaxed);
|
||||
wait();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
atomic<ptrdiff_t> _Counter;
|
||||
};
|
||||
|
||||
_STD_END
|
||||
|
||||
#pragma pop_macro("new")
|
||||
_STL_RESTORE_CLANG_WARNINGS
|
||||
#pragma warning(pop)
|
||||
#pragma pack(pop)
|
||||
#endif // ^^^ _HAS_CXX20 ^^^
|
||||
|
||||
#endif // _STL_COMPILER_PREPROCESSOR
|
||||
#endif // _LATCH_
|
|
@ -0,0 +1,311 @@
|
|||
// semaphore standard header
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#pragma once
|
||||
#ifndef _SEMAPHORE_
|
||||
#define _SEMAPHORE_
|
||||
#include <yvals.h>
|
||||
#if _STL_COMPILER_PREPROCESSOR
|
||||
|
||||
#ifdef _M_CEE_PURE
|
||||
#error <semaphore> is not supported when compiling with /clr:pure.
|
||||
#endif // _M_CEE_PURE
|
||||
|
||||
#if !_HAS_CXX20
|
||||
#pragma message("The contents of <semaphore> are available only with C++20 or later.")
|
||||
#else // ^^^ !_HAS_CXX20 / _HAS_CXX20 vvv
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <limits.h>
|
||||
|
||||
#pragma pack(push, _CRT_PACKING)
|
||||
#pragma warning(push, _STL_WARNING_LEVEL)
|
||||
#pragma warning(disable : _STL_DISABLED_WARNINGS)
|
||||
_STL_DISABLE_CLANG_WARNINGS
|
||||
#pragma push_macro("new")
|
||||
#undef new
|
||||
|
||||
_STD_BEGIN
|
||||
|
||||
template <class _Rep, class _Period>
|
||||
_NODISCARD unsigned long long _Semaphore_deadline(const chrono::duration<_Rep, _Period>& _Rel_time) {
|
||||
return __std_atomic_wait_get_deadline(
|
||||
chrono::duration_cast<chrono::duration<unsigned long long, milli>>(_Rel_time).count());
|
||||
}
|
||||
|
||||
template <class _Clock, class _Duration>
|
||||
_NODISCARD unsigned long _Semaphore_remaining_timeout(const chrono::time_point<_Clock, _Duration>& _Abs_time) {
|
||||
const auto _Now = _Clock::now();
|
||||
if (_Now >= _Abs_time) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto _Rel_time = chrono::ceil<chrono::milliseconds>(_Abs_time - _Now);
|
||||
static constexpr chrono::milliseconds _Ten_days{chrono::hours{24 * 10}};
|
||||
static_assert(_Ten_days.count() < ULONG_MAX, "Bad sizing assumption");
|
||||
if (_Rel_time >= _Ten_days) {
|
||||
return static_cast<unsigned long>(_Ten_days.count());
|
||||
}
|
||||
|
||||
return static_cast<unsigned long>(_Rel_time.count());
|
||||
}
|
||||
|
||||
inline constexpr ptrdiff_t _Semaphore_max = (1ULL << (sizeof(ptrdiff_t) * CHAR_BIT - 1)) - 1;
|
||||
|
||||
template <ptrdiff_t _Least_max_value = _Semaphore_max>
|
||||
class counting_semaphore {
|
||||
public:
|
||||
_NODISCARD static constexpr ptrdiff_t(max)() noexcept {
|
||||
return _Least_max_value;
|
||||
}
|
||||
|
||||
constexpr explicit counting_semaphore(const ptrdiff_t _Desired) noexcept /* strengthened */
|
||||
: _Counter(_Desired) {
|
||||
_STL_VERIFY(_Desired >= 0 && _Desired <= _Least_max_value,
|
||||
"Precondition: desired >= 0, and desired <= max() (N4861 [thread.sema.cnt]/5)");
|
||||
}
|
||||
|
||||
counting_semaphore(const counting_semaphore&) = delete;
|
||||
counting_semaphore& operator=(const counting_semaphore&) = delete;
|
||||
|
||||
void release(ptrdiff_t _Update = 1) noexcept /* strengthened */ {
|
||||
if (_Update == 0) {
|
||||
return;
|
||||
}
|
||||
_STL_VERIFY(_Update > 0 && _Update <= _Least_max_value,
|
||||
"Precondition: update >= 0, and update <= max() - counter (N4861 [thread.sema.cnt]/8)");
|
||||
|
||||
// We need to notify (wake) at least _Update waiting threads.
|
||||
// Errors towards waking more cannot be always avoided, but they are performance issues.
|
||||
// Errors towards waking fewer must be avoided, as they are correctness issues.
|
||||
|
||||
// release thread: Increment semaphore counter, then load waiting counter;
|
||||
// acquire thread: Increment waiting counter, then load semaphore counter;
|
||||
|
||||
// memory_order_seq_cst for all four operations guarantees that the release thread loads
|
||||
// the incremented value, or the acquire thread loads the incremented value, or both, but not neither.
|
||||
// memory_order_seq_cst might be superfluous for some hardware mappings of the C++ memory model,
|
||||
// but from the point of view of the C++ memory model itself it is needed; weaker orders don't work.
|
||||
|
||||
const ptrdiff_t _Prev = _Counter.fetch_add(static_cast<ptrdiff_t>(_Update));
|
||||
_STL_VERIFY(_Prev + _Update > 0 && _Prev + _Update <= _Least_max_value,
|
||||
"Precondition: update <= max() - counter (N4861 [thread.sema.cnt]/8)");
|
||||
|
||||
const ptrdiff_t _Waiting_upper_bound = _Waiting.load();
|
||||
|
||||
if (_Waiting_upper_bound == 0) {
|
||||
// Definitely no one is waiting
|
||||
} else if (_Waiting_upper_bound <= _Update) {
|
||||
// No more waiting threads than update, can wake everyone.
|
||||
_Counter.notify_all();
|
||||
} else {
|
||||
// Wake at most _Update. Though repeated notify_one() is somewhat less efficient than single notify_all(),
|
||||
// the amount of OS calls is still the same; the benefit from trying not to wake unnecessary threads
|
||||
// is expected to be greater than the loss on extra calls and atomic operations.
|
||||
for (; _Update != 0; --_Update) {
|
||||
_Counter.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _Wait(const unsigned long _Remaining_timeout) noexcept {
|
||||
// See the comment in release()
|
||||
_Waiting.fetch_add(1);
|
||||
ptrdiff_t _Current = _Counter.load();
|
||||
if (_Current == 0) {
|
||||
__std_atomic_wait_direct(&_Counter, &_Current, sizeof(_Current), _Remaining_timeout);
|
||||
}
|
||||
_Waiting.fetch_sub(1, memory_order_relaxed);
|
||||
}
|
||||
|
||||
void acquire() noexcept /* strengthened */ {
|
||||
ptrdiff_t _Current = _Counter.load(memory_order_relaxed);
|
||||
for (;;) {
|
||||
while (_Current == 0) {
|
||||
_Wait(_Atomic_wait_no_timeout);
|
||||
_Current = _Counter.load(memory_order_relaxed);
|
||||
}
|
||||
_STL_VERIFY(_Current > 0 && _Current <= _Least_max_value,
|
||||
"Invariant: counter >= 0, and counter <= max() "
|
||||
"possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)");
|
||||
|
||||
// "happens after release" ordering is provided by this CAS, so loads and waits can be relaxed
|
||||
if (_Counter.compare_exchange_weak(_Current, _Current - 1)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_NODISCARD bool try_acquire() noexcept {
|
||||
ptrdiff_t _Current = _Counter.load();
|
||||
if (_Current == 0) {
|
||||
return false;
|
||||
}
|
||||
_STL_VERIFY(_Current > 0 && _Current <= _Least_max_value,
|
||||
"Invariant: counter >= 0, and counter <= max() "
|
||||
"possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)");
|
||||
|
||||
return _Counter.compare_exchange_weak(_Current, _Current - 1);
|
||||
}
|
||||
|
||||
template <class _Rep, class _Period>
|
||||
_NODISCARD bool try_acquire_for(const chrono::duration<_Rep, _Period>& _Rel_time) {
|
||||
auto _Deadline = _Semaphore_deadline(_Rel_time);
|
||||
ptrdiff_t _Current = _Counter.load(memory_order_relaxed);
|
||||
for (;;) {
|
||||
while (_Current == 0) {
|
||||
const auto _Remaining_timeout = __std_atomic_wait_get_remaining_timeout(_Deadline);
|
||||
if (_Remaining_timeout == 0) {
|
||||
return false;
|
||||
}
|
||||
_Wait(_Remaining_timeout);
|
||||
_Current = _Counter.load(memory_order_relaxed);
|
||||
}
|
||||
_STL_VERIFY(_Current > 0 && _Current <= _Least_max_value,
|
||||
"Invariant: counter >= 0, and counter <= max() "
|
||||
"possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)");
|
||||
|
||||
// "happens after release" ordering is provided by this CAS, so loads and waits can be relaxed
|
||||
if (_Counter.compare_exchange_weak(_Current, _Current - 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class _Clock, class _Duration>
|
||||
_NODISCARD bool try_acquire_until(const chrono::time_point<_Clock, _Duration>& _Abs_time) {
|
||||
ptrdiff_t _Current = _Counter.load(memory_order_relaxed);
|
||||
for (;;) {
|
||||
while (_Current == 0) {
|
||||
const unsigned long _Remaining_timeout = _Semaphore_remaining_timeout(_Abs_time);
|
||||
if (_Remaining_timeout == 0) {
|
||||
return false;
|
||||
}
|
||||
_Wait(_Remaining_timeout);
|
||||
_Current = _Counter.load(memory_order_relaxed);
|
||||
}
|
||||
_STL_VERIFY(_Current > 0 && _Current <= _Least_max_value,
|
||||
"Invariant: counter >= 0, and counter <= max() "
|
||||
"possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)");
|
||||
|
||||
// "happens after release" ordering is provided by this CAS, so loads and waits can be relaxed
|
||||
if (_Counter.compare_exchange_weak(_Current, _Current - 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
atomic<ptrdiff_t> _Counter;
|
||||
atomic<ptrdiff_t> _Waiting;
|
||||
};
|
||||
|
||||
template <>
|
||||
class counting_semaphore<1> {
|
||||
public:
|
||||
_NODISCARD static constexpr ptrdiff_t(max)() noexcept {
|
||||
return 1;
|
||||
}
|
||||
|
||||
constexpr explicit counting_semaphore(const ptrdiff_t _Desired) noexcept /* strengthened */
|
||||
: _Counter(static_cast<unsigned char>(_Desired)) {
|
||||
_STL_VERIFY((_Desired & ~1) == 0, "Precondition: desired >= 0, and desired <= max() "
|
||||
"(N4861 [thread.sema.cnt]/5)");
|
||||
}
|
||||
|
||||
counting_semaphore(const counting_semaphore&) = delete;
|
||||
counting_semaphore& operator=(const counting_semaphore&) = delete;
|
||||
|
||||
void release(const ptrdiff_t _Update = 1) noexcept /* strengthened */ {
|
||||
if (_Update == 0) {
|
||||
return;
|
||||
}
|
||||
_STL_VERIFY(_Update == 1, "Precondition: update >= 0, "
|
||||
"and update <= max() - counter (N4861 [thread.sema.cnt]/8)");
|
||||
// TRANSITION, GH-1133: should be memory_order_release
|
||||
_Counter.store(1);
|
||||
_Counter.notify_one();
|
||||
}
|
||||
|
||||
void acquire() noexcept /* strengthened */ {
|
||||
for (;;) {
|
||||
// "happens after release" ordering is provided by this exchange, so loads and waits can be relaxed
|
||||
// TRANSITION, GH-1133: should be memory_order_acquire
|
||||
unsigned char _Prev = _Counter.exchange(0);
|
||||
if (_Prev == 1) {
|
||||
break;
|
||||
}
|
||||
_STL_VERIFY(_Prev == 0, "Invariant: semaphore counter is non-negative and doesn't exceed max(), "
|
||||
"possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)");
|
||||
_Counter.wait(0, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
_NODISCARD bool try_acquire() noexcept {
|
||||
// TRANSITION, GH-1133: should be memory_order_acquire
|
||||
unsigned char _Prev = _Counter.exchange(0);
|
||||
_STL_VERIFY((_Prev & ~1) == 0, "Invariant: semaphore counter is non-negative and doesn't exceed max(), "
|
||||
"possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)");
|
||||
return reinterpret_cast<const bool&>(_Prev);
|
||||
}
|
||||
|
||||
template <class _Rep, class _Period>
|
||||
_NODISCARD bool try_acquire_for(const chrono::duration<_Rep, _Period>& _Rel_time) {
|
||||
auto _Deadline = _Semaphore_deadline(_Rel_time);
|
||||
for (;;) {
|
||||
// "happens after release" ordering is provided by this exchange, so loads and waits can be relaxed
|
||||
// TRANSITION, GH-1133: should be memory_order_acquire
|
||||
unsigned char _Prev = _Counter.exchange(0);
|
||||
if (_Prev == 1) {
|
||||
return true;
|
||||
}
|
||||
_STL_VERIFY(_Prev == 0, "Invariant: semaphore counter is non-negative and doesn't exceed max(), "
|
||||
"possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)");
|
||||
const auto _Remaining_timeout = __std_atomic_wait_get_remaining_timeout(_Deadline);
|
||||
if (_Remaining_timeout == 0) {
|
||||
return false;
|
||||
}
|
||||
__std_atomic_wait_direct(&_Counter, &_Prev, sizeof(_Prev), _Remaining_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
template <class _Clock, class _Duration>
|
||||
_NODISCARD bool try_acquire_until(const chrono::time_point<_Clock, _Duration>& _Abs_time) {
|
||||
for (;;) {
|
||||
// "happens after release" ordering is provided by this exchange, so loads and waits can be relaxed
|
||||
// TRANSITION, GH-1133: should be memory_order_acquire
|
||||
unsigned char _Prev = _Counter.exchange(0);
|
||||
if (_Prev == 1) {
|
||||
return true;
|
||||
}
|
||||
_STL_VERIFY(_Prev == 0, "Invariant: semaphore counter is non-negative and doesn't exceed max(), "
|
||||
"possibly caused by preconditions violation (N4861 [thread.sema.cnt]/8)");
|
||||
|
||||
const unsigned long _Remaining_timeout = _Semaphore_remaining_timeout(_Abs_time);
|
||||
if (_Remaining_timeout == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
__std_atomic_wait_direct(&_Counter, &_Prev, sizeof(_Prev), _Remaining_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
atomic<unsigned char> _Counter;
|
||||
};
|
||||
|
||||
using binary_semaphore = counting_semaphore<1>;
|
||||
|
||||
_STD_END
|
||||
|
||||
#pragma pop_macro("new")
|
||||
_STL_RESTORE_CLANG_WARNINGS
|
||||
#pragma warning(pop)
|
||||
#pragma pack(pop)
|
||||
#endif // ^^^ _HAS_CXX20 ^^^
|
||||
|
||||
#endif // _STL_COMPILER_PREPROCESSOR
|
||||
#endif // _SEMAPHORE_
|
|
@ -185,7 +185,6 @@
|
|||
// P1115R3 erase()/erase_if() Return size_type
|
||||
// P1123R0 Atomic Compare-And-Exchange With Padding Bits For atomic_ref
|
||||
// P1135R6 The C++20 Synchronization Library
|
||||
// (partially implemented)
|
||||
// P1207R4 Movability Of Single-Pass Iterators
|
||||
// (partially implemented)
|
||||
// P1209R0 erase_if(), erase()
|
||||
|
@ -204,6 +203,7 @@
|
|||
// P1690R1 Refining Heterogeneous Lookup For Unordered Containers
|
||||
// P1716R3 Range Comparison Algorithms Are Over-Constrained
|
||||
// P1754R1 Rename Concepts To standard_case
|
||||
// P1865R1 Adding max() To latch And barrier
|
||||
// P1870R1 Rename forwarding-range To borrowed_range (Was safe_range before LWG-3379)
|
||||
// P1871R1 disable_sized_sentinel_for
|
||||
// P1872R0 span Should Have size_type, Not index_type
|
||||
|
@ -1145,6 +1145,7 @@
|
|||
#define __cpp_lib_atomic_ref 201806L
|
||||
#define __cpp_lib_atomic_shared_ptr 201711L
|
||||
#define __cpp_lib_atomic_wait 201907L
|
||||
#define __cpp_lib_barrier 201907L
|
||||
#define __cpp_lib_bind_front 201907L
|
||||
#define __cpp_lib_bit_cast 201806L
|
||||
#define __cpp_lib_bitops 201907L
|
||||
|
@ -1185,9 +1186,11 @@
|
|||
#define __cpp_lib_interpolate 201902L
|
||||
#define __cpp_lib_is_constant_evaluated 201811L
|
||||
#define __cpp_lib_is_nothrow_convertible 201806L
|
||||
#define __cpp_lib_latch 201907L
|
||||
#define __cpp_lib_list_remove_return_type 201806L
|
||||
#define __cpp_lib_math_constants 201907L
|
||||
#define __cpp_lib_remove_cvref 201711L
|
||||
#define __cpp_lib_semaphore 201907L
|
||||
#define __cpp_lib_shift 201806L
|
||||
#define __cpp_lib_span 202002L
|
||||
#define __cpp_lib_ssize 201902L
|
||||
|
|
|
@ -469,26 +469,6 @@ std/language.support/support.limits/support.limits.general/functional.version.pa
|
|||
std/language.support/support.limits/support.limits.general/iterator.version.pass.cpp FAIL
|
||||
std/language.support/support.limits/support.limits.general/memory.version.pass.cpp FAIL
|
||||
|
||||
# C++20 P1135R6 "The C++20 Synchronization Library"
|
||||
std/thread/thread.barrier/arrive.pass.cpp FAIL
|
||||
std/thread/thread.barrier/arrive_and_drop.pass.cpp FAIL
|
||||
std/thread/thread.barrier/arrive_and_wait.pass.cpp FAIL
|
||||
std/thread/thread.barrier/completion.pass.cpp FAIL
|
||||
std/thread/thread.barrier/max.pass.cpp FAIL
|
||||
std/thread/thread.barrier/version.pass.cpp FAIL
|
||||
std/thread/thread.latch/arrive_and_wait.pass.cpp FAIL
|
||||
std/thread/thread.latch/count_down.pass.cpp FAIL
|
||||
std/thread/thread.latch/max.pass.cpp FAIL
|
||||
std/thread/thread.latch/try_wait.pass.cpp FAIL
|
||||
std/thread/thread.latch/version.pass.cpp FAIL
|
||||
std/thread/thread.semaphore/acquire.pass.cpp FAIL
|
||||
std/thread/thread.semaphore/binary.pass.cpp FAIL
|
||||
std/thread/thread.semaphore/max.pass.cpp FAIL
|
||||
std/thread/thread.semaphore/release.pass.cpp FAIL
|
||||
std/thread/thread.semaphore/timed.pass.cpp FAIL
|
||||
std/thread/thread.semaphore/try_acquire.pass.cpp FAIL
|
||||
std/thread/thread.semaphore/version.pass.cpp FAIL
|
||||
|
||||
|
||||
# *** MISSING COMPILER FEATURES ***
|
||||
# Nothing here! :-)
|
||||
|
@ -647,6 +627,19 @@ std/thread/thread.threads/thread.thread.class/thread.thread.member/join.pass.cpp
|
|||
|
||||
|
||||
# *** LIKELY BOGUS TESTS ***
|
||||
# "error: _LIBCPP_VERSION not defined"
|
||||
std/thread/thread.barrier/version.pass.cpp FAIL
|
||||
std/thread/thread.latch/version.pass.cpp FAIL
|
||||
std/thread/thread.semaphore/version.pass.cpp FAIL
|
||||
|
||||
# "error C3861: 'assert': identifier not found"
|
||||
std/thread/thread.semaphore/timed.pass.cpp FAIL
|
||||
std/thread/thread.semaphore/try_acquire.pass.cpp FAIL
|
||||
|
||||
# pass lambda without noexcept to barrier
|
||||
std/thread/thread.barrier/completion.pass.cpp FAIL
|
||||
std/thread/thread.barrier/max.pass.cpp FAIL
|
||||
|
||||
# Test bug/LEWG issue or STL bug. See GH-519 "<cmath>: signbit() misses overloads for integer types".
|
||||
std/depr/depr.c.headers/math_h.pass.cpp FAIL
|
||||
std/numerics/c.math/cmath.pass.cpp FAIL
|
||||
|
|
|
@ -469,26 +469,6 @@ language.support\support.limits\support.limits.general\functional.version.pass.c
|
|||
language.support\support.limits\support.limits.general\iterator.version.pass.cpp
|
||||
language.support\support.limits\support.limits.general\memory.version.pass.cpp
|
||||
|
||||
# C++20 P1135R6 "The C++20 Synchronization Library"
|
||||
thread\thread.barrier\arrive.pass.cpp
|
||||
thread\thread.barrier\arrive_and_drop.pass.cpp
|
||||
thread\thread.barrier\arrive_and_wait.pass.cpp
|
||||
thread\thread.barrier\completion.pass.cpp
|
||||
thread\thread.barrier\max.pass.cpp
|
||||
thread\thread.barrier\version.pass.cpp
|
||||
thread\thread.latch\arrive_and_wait.pass.cpp
|
||||
thread\thread.latch\count_down.pass.cpp
|
||||
thread\thread.latch\max.pass.cpp
|
||||
thread\thread.latch\try_wait.pass.cpp
|
||||
thread\thread.latch\version.pass.cpp
|
||||
thread\thread.semaphore\acquire.pass.cpp
|
||||
thread\thread.semaphore\binary.pass.cpp
|
||||
thread\thread.semaphore\max.pass.cpp
|
||||
thread\thread.semaphore\release.pass.cpp
|
||||
thread\thread.semaphore\timed.pass.cpp
|
||||
thread\thread.semaphore\try_acquire.pass.cpp
|
||||
thread\thread.semaphore\version.pass.cpp
|
||||
|
||||
|
||||
# *** MISSING COMPILER FEATURES ***
|
||||
# Nothing here! :-)
|
||||
|
@ -647,6 +627,19 @@ thread\thread.threads\thread.thread.class\thread.thread.member\join.pass.cpp
|
|||
|
||||
|
||||
# *** LIKELY BOGUS TESTS ***
|
||||
# "error: _LIBCPP_VERSION not defined"
|
||||
thread\thread.barrier\version.pass.cpp
|
||||
thread\thread.latch\version.pass.cpp
|
||||
thread\thread.semaphore\version.pass.cpp
|
||||
|
||||
# "error C3861: 'assert': identifier not found"
|
||||
thread\thread.semaphore\timed.pass.cpp
|
||||
thread\thread.semaphore\try_acquire.pass.cpp
|
||||
|
||||
# pass lambda without noexcept to barrier
|
||||
thread\thread.barrier\completion.pass.cpp
|
||||
thread\thread.barrier\max.pass.cpp
|
||||
|
||||
# Test bug/LEWG issue or STL bug. See GH-519 "<cmath>: signbit() misses overloads for integer types".
|
||||
depr\depr.c.headers\math_h.pass.cpp
|
||||
numerics\c.math\cmath.pass.cpp
|
||||
|
|
|
@ -329,6 +329,9 @@ tests\P1032R1_miscellaneous_constexpr
|
|||
tests\P1135R6_atomic_flag_test
|
||||
tests\P1135R6_atomic_wait
|
||||
tests\P1135R6_atomic_wait_vista
|
||||
tests\P1135R6_barrier
|
||||
tests\P1135R6_latch
|
||||
tests\P1135R6_semaphore
|
||||
tests\P1165R1_consistently_propagating_stateful_allocators
|
||||
tests\P1423R3_char8_t_remediation
|
||||
tests\P1645R1_constexpr_numeric
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
RUNALL_INCLUDE ..\usual_latest_matrix.lst
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include <atomic>
|
||||
#include <barrier>
|
||||
#include <cassert>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
void test() {
|
||||
std::barrier b(2);
|
||||
|
||||
std::atomic<int> c{0};
|
||||
|
||||
std::thread t1([&] {
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
auto token = b.arrive();
|
||||
b.wait(std::move(token));
|
||||
c.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
std::thread t2([&] {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
b.arrive_and_wait();
|
||||
c.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
b.arrive_and_drop();
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
assert(c.load(std::memory_order_relaxed) == 8);
|
||||
}
|
||||
|
||||
void test_with_functor() {
|
||||
std::atomic<int> c{0};
|
||||
std::atomic<int> called_times{0};
|
||||
|
||||
struct Functor {
|
||||
void operator()() noexcept {
|
||||
switch (called_times->fetch_add(1, std::memory_order_relaxed) + 1) {
|
||||
case 1:
|
||||
assert(c->load(std::memory_order_relaxed) == 0);
|
||||
break;
|
||||
case 2:
|
||||
assert(c->load(std::memory_order_relaxed) == 2);
|
||||
break;
|
||||
case 3:
|
||||
assert(c->load(std::memory_order_relaxed) == 4);
|
||||
break;
|
||||
case 4:
|
||||
assert(c->load(std::memory_order_relaxed) == 6);
|
||||
break;
|
||||
case 5:
|
||||
assert(c->load(std::memory_order_relaxed) == 7);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<int>* called_times;
|
||||
std::atomic<int>* c;
|
||||
} f = {&called_times, &c};
|
||||
|
||||
std::barrier b(2, f);
|
||||
|
||||
std::thread t1([&] {
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
auto token = b.arrive();
|
||||
b.wait(std::move(token));
|
||||
c.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
std::thread t2([&] {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
b.arrive_and_wait();
|
||||
c.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
b.arrive_and_drop();
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
assert(c.load(std::memory_order_relaxed) == 8);
|
||||
assert(called_times.load(std::memory_order_relaxed) == 5);
|
||||
}
|
||||
|
||||
|
||||
void test_token() {
|
||||
std::atomic<int> called_times{0};
|
||||
|
||||
auto f = [&]() noexcept { called_times.fetch_add(1, std::memory_order_relaxed); };
|
||||
|
||||
std::barrier b(2, f);
|
||||
auto t1 = b.arrive();
|
||||
auto t2 = std::move(t1);
|
||||
|
||||
assert(called_times.load(std::memory_order_relaxed) == 0);
|
||||
auto t3 = b.arrive();
|
||||
auto t4 = std::move(t3);
|
||||
|
||||
assert(called_times.load(std::memory_order_relaxed) == 1);
|
||||
b.wait(std::move(t4));
|
||||
assert(called_times.load(std::memory_order_relaxed) == 1);
|
||||
b.wait(std::move(t2));
|
||||
assert(called_times.load(std::memory_order_relaxed) == 1);
|
||||
}
|
||||
|
||||
void barrier_callback_function() noexcept {}
|
||||
|
||||
void test_functor_types() {
|
||||
struct f1 {
|
||||
void operator()() noexcept {}
|
||||
|
||||
f1(int, int, int) {}
|
||||
|
||||
f1(f1&&) noexcept = default;
|
||||
f1& operator=(f1&&) = delete;
|
||||
};
|
||||
std::barrier b1{1, f1{0, 0, 0}};
|
||||
b1.arrive_and_wait();
|
||||
|
||||
std::barrier b2{1, barrier_callback_function};
|
||||
b2.arrive_and_wait();
|
||||
|
||||
std::barrier b3{1, []() noexcept {}};
|
||||
b3.arrive_and_wait();
|
||||
}
|
||||
|
||||
int main() {
|
||||
static_assert(std::barrier<>::max() >= 5, "barrier should support some number of arrivals");
|
||||
|
||||
test();
|
||||
test_with_functor();
|
||||
test_token();
|
||||
test_functor_types();
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
RUNALL_INCLUDE ..\usual_latest_matrix.lst
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include <cassert>
|
||||
#include <latch>
|
||||
#include <thread>
|
||||
|
||||
void test(const bool release_wait) {
|
||||
std::latch l(5);
|
||||
|
||||
std::thread t1([&] { l.wait(); });
|
||||
|
||||
std::thread t2([&] { l.arrive_and_wait(2); });
|
||||
|
||||
l.count_down();
|
||||
|
||||
if (release_wait) {
|
||||
l.arrive_and_wait(2);
|
||||
} else {
|
||||
l.count_down(2);
|
||||
}
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
}
|
||||
|
||||
int main() {
|
||||
static_assert(std::latch::max() >= 5, "latch should support some number of count downs");
|
||||
|
||||
test(true);
|
||||
test(false);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
RUNALL_INCLUDE ..\usual_latest_matrix.lst
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <latch>
|
||||
#include <limits>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
void wait_and_expect(std::atomic<int>& v, const int val, const std::chrono::milliseconds delay_duration) {
|
||||
#ifdef CAN_FAIL_ON_TIMING_ASSUMPTION
|
||||
std::this_thread::sleep_for(delay_duration);
|
||||
assert(v.load() == val);
|
||||
#else // ^^^ CAN_FAIL_ON_TIMING_ASSUMPTION / !CAN_FAIL_ON_TIMING_ASSUMPTION vvv
|
||||
while (v.load() < val) {
|
||||
std::this_thread::sleep_for(delay_duration);
|
||||
}
|
||||
assert(v.load() == val);
|
||||
#endif // ^^^ !CAN_FAIL_ON_TIMING_ASSUMPTION ^^^
|
||||
}
|
||||
|
||||
void test_counting_semaphore_count(const std::chrono::milliseconds delay_duration) {
|
||||
std::latch start{4};
|
||||
|
||||
std::counting_semaphore<4> s{2};
|
||||
std::atomic<int> v{0};
|
||||
|
||||
auto thread_function = [&] {
|
||||
start.arrive_and_wait();
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
s.acquire();
|
||||
v.fetch_add(1);
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t1{thread_function};
|
||||
std::thread t2{thread_function};
|
||||
std::thread t3{thread_function};
|
||||
|
||||
start.arrive_and_wait();
|
||||
|
||||
wait_and_expect(v, 2, delay_duration);
|
||||
|
||||
s.release();
|
||||
|
||||
wait_and_expect(v, 3, delay_duration);
|
||||
|
||||
s.release(4);
|
||||
|
||||
wait_and_expect(v, 7, delay_duration);
|
||||
|
||||
s.release(4);
|
||||
|
||||
wait_and_expect(v, 9, delay_duration);
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
}
|
||||
|
||||
void test_binary_semaphore_count(const std::chrono::milliseconds delay_duration) {
|
||||
std::latch start{3};
|
||||
|
||||
std::binary_semaphore s{1};
|
||||
|
||||
std::atomic<int> v{0};
|
||||
|
||||
auto thread_function = [&] {
|
||||
start.arrive_and_wait();
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
s.acquire();
|
||||
v.fetch_add(1);
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t1{thread_function};
|
||||
std::thread t2{thread_function};
|
||||
|
||||
start.arrive_and_wait();
|
||||
|
||||
wait_and_expect(v, 1, delay_duration);
|
||||
|
||||
s.release();
|
||||
wait_and_expect(v, 2, delay_duration);
|
||||
|
||||
s.release();
|
||||
wait_and_expect(v, 3, delay_duration);
|
||||
|
||||
s.release();
|
||||
wait_and_expect(v, 4, delay_duration);
|
||||
|
||||
s.release();
|
||||
wait_and_expect(v, 4, delay_duration);
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
}
|
||||
|
||||
template <class Semaphore>
|
||||
void test_semaphore_wait_for(const std::chrono::milliseconds delay_duration) {
|
||||
std::latch start{2};
|
||||
|
||||
Semaphore s{0};
|
||||
|
||||
std::thread t([&] {
|
||||
start.arrive_and_wait();
|
||||
|
||||
assert(s.try_acquire_for(delay_duration));
|
||||
assert(!s.try_acquire_for(delay_duration * 16));
|
||||
});
|
||||
|
||||
start.arrive_and_wait();
|
||||
|
||||
s.release();
|
||||
|
||||
std::this_thread::sleep_for(delay_duration * 4);
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
template <class Semaphore>
|
||||
void test_semaphore_wait_until(const std::chrono::milliseconds delay_duration) {
|
||||
std::latch start{2};
|
||||
|
||||
Semaphore s{0};
|
||||
|
||||
std::thread t([&] {
|
||||
start.arrive_and_wait();
|
||||
|
||||
assert(s.try_acquire_until(std::chrono::steady_clock::now() + delay_duration));
|
||||
assert(!s.try_acquire_until(std::chrono::steady_clock::now() + delay_duration * 8));
|
||||
});
|
||||
|
||||
start.arrive_and_wait();
|
||||
|
||||
s.release();
|
||||
|
||||
std::this_thread::sleep_for(delay_duration * 4);
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
int main() {
|
||||
constexpr auto max = std::numeric_limits<std::ptrdiff_t>::max();
|
||||
|
||||
static_assert(std::counting_semaphore<max>::max() >= max, "semaphore should support some number of count downs");
|
||||
static_assert(std::counting_semaphore<5>::max() >= 5, "semaphore should support some number of count downs");
|
||||
static_assert(std::binary_semaphore::max() >= 1, "semaphore should support some number of count downs");
|
||||
|
||||
constexpr auto delay_duration = 200ms;
|
||||
|
||||
test_counting_semaphore_count(delay_duration);
|
||||
test_binary_semaphore_count(delay_duration);
|
||||
|
||||
#ifdef CAN_FAIL_ON_TIMING_ASSUMPTION
|
||||
test_semaphore_wait_for<std::counting_semaphore<>>(delay_duration);
|
||||
test_semaphore_wait_until<std::counting_semaphore<>>(delay_duration);
|
||||
test_semaphore_wait_for<std::binary_semaphore>(delay_duration);
|
||||
test_semaphore_wait_until<std::binary_semaphore>(delay_duration);
|
||||
#endif // CAN_FAIL_ON_TIMING_ASSUMPTION
|
||||
}
|
|
@ -189,6 +189,20 @@ STATIC_ASSERT(__cpp_lib_atomic_wait == 201907L);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#if _HAS_CXX20
|
||||
#ifndef __cpp_lib_barrier
|
||||
#error __cpp_lib_barrier is not defined
|
||||
#elif __cpp_lib_barrier != 201907L
|
||||
#error __cpp_lib_barrier is not 201907L
|
||||
#else
|
||||
STATIC_ASSERT(__cpp_lib_barrier == 201907L);
|
||||
#endif
|
||||
#else
|
||||
#ifdef __cpp_lib_barrier
|
||||
#error __cpp_lib_barrier is defined
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if _HAS_CXX20
|
||||
#ifndef __cpp_lib_bind_front
|
||||
#error __cpp_lib_bind_front is not defined
|
||||
|
@ -848,6 +862,20 @@ STATIC_ASSERT(__cpp_lib_is_swappable == 201603L);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#if _HAS_CXX20
|
||||
#ifndef __cpp_lib_latch
|
||||
#error __cpp_lib_latch is not defined
|
||||
#elif __cpp_lib_latch != 201907L
|
||||
#error __cpp_lib_latch is not 201907L
|
||||
#else
|
||||
STATIC_ASSERT(__cpp_lib_latch == 201907L);
|
||||
#endif
|
||||
#else
|
||||
#ifdef __cpp_lib_latch
|
||||
#error __cpp_lib_latch is defined
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if _HAS_CXX17
|
||||
#ifndef __cpp_lib_launder
|
||||
#error __cpp_lib_launder is not defined
|
||||
|
@ -1116,6 +1144,20 @@ STATIC_ASSERT(__cpp_lib_scoped_lock == 201703L);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#if _HAS_CXX20
|
||||
#ifndef __cpp_lib_semaphore
|
||||
#error __cpp_lib_semaphore is not defined
|
||||
#elif __cpp_lib_semaphore != 201907L
|
||||
#error __cpp_lib_semaphore is not 201907L
|
||||
#else
|
||||
STATIC_ASSERT(__cpp_lib_semaphore == 201907L);
|
||||
#endif
|
||||
#else
|
||||
#ifdef __cpp_lib_semaphore
|
||||
#error __cpp_lib_semaphore is defined
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef __cpp_lib_shared_mutex
|
||||
#error __cpp_lib_shared_mutex is not defined
|
||||
#elif __cpp_lib_shared_mutex != 201505L
|
||||
|
|
|
@ -9,6 +9,7 @@ PM_CL="/DMEOW_HEADER=algorithm"
|
|||
PM_CL="/DMEOW_HEADER=any"
|
||||
PM_CL="/DMEOW_HEADER=array"
|
||||
PM_CL="/DMEOW_HEADER=atomic"
|
||||
PM_CL="/DMEOW_HEADER=barrier"
|
||||
PM_CL="/DMEOW_HEADER=bit"
|
||||
PM_CL="/DMEOW_HEADER=bitset"
|
||||
PM_CL="/DMEOW_HEADER=charconv"
|
||||
|
@ -35,6 +36,7 @@ PM_CL="/DMEOW_HEADER=iostream"
|
|||
PM_CL="/DMEOW_HEADER=iso646.h"
|
||||
PM_CL="/DMEOW_HEADER=istream"
|
||||
PM_CL="/DMEOW_HEADER=iterator"
|
||||
PM_CL="/DMEOW_HEADER=latch"
|
||||
PM_CL="/DMEOW_HEADER=limits"
|
||||
PM_CL="/DMEOW_HEADER=list"
|
||||
PM_CL="/DMEOW_HEADER=locale"
|
||||
|
@ -53,6 +55,7 @@ PM_CL="/DMEOW_HEADER=ranges"
|
|||
PM_CL="/DMEOW_HEADER=ratio"
|
||||
PM_CL="/DMEOW_HEADER=regex"
|
||||
PM_CL="/DMEOW_HEADER=scoped_allocator"
|
||||
PM_CL="/DMEOW_HEADER=semaphore"
|
||||
PM_CL="/DMEOW_HEADER=set"
|
||||
PM_CL="/DMEOW_HEADER=shared_mutex"
|
||||
PM_CL="/DMEOW_HEADER=span"
|
||||
|
|
Загрузка…
Ссылка в новой задаче