зеркало из https://github.com/microsoft/STL.git
285 строки
9.8 KiB
C++
285 строки
9.8 KiB
C++
// condition_variable standard header
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#pragma once
|
|
#ifndef _CONDITION_VARIABLE_
|
|
#define _CONDITION_VARIABLE_
|
|
#include <yvals_core.h>
|
|
#if _STL_COMPILER_PREPROCESSOR
|
|
#include <chrono>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <xthreads.h>
|
|
#if _HAS_CXX20
|
|
#include <stop_token>
|
|
#endif // _HAS_CXX20
|
|
|
|
#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
|
|
|
|
#ifdef _M_CEE
|
|
#error <condition_variable> is not supported when compiling with /clr or /clr:pure.
|
|
#endif // _M_CEE
|
|
|
|
_STD_BEGIN
|
|
template <class _Lock>
|
|
struct _NODISCARD _Unlock_guard {
|
|
explicit _Unlock_guard(_Lock& _Mtx_) : _Mtx(_Mtx_) {
|
|
_Mtx.unlock();
|
|
}
|
|
|
|
~_Unlock_guard() noexcept /* terminates */ {
|
|
// relock mutex or terminate()
|
|
// condition_variable_any wait functions are required to terminate if
|
|
// the mutex cannot be relocked;
|
|
// we slam into noexcept here for easier user debugging.
|
|
_Mtx.lock();
|
|
}
|
|
|
|
_Unlock_guard(const _Unlock_guard&) = delete;
|
|
_Unlock_guard& operator=(const _Unlock_guard&) = delete;
|
|
|
|
private:
|
|
_Lock& _Mtx;
|
|
};
|
|
|
|
class condition_variable_any { // class for waiting for conditions with any kind of mutex
|
|
public:
|
|
condition_variable_any() : _Myptr{_STD make_shared<mutex>()} {
|
|
_Cnd_init_in_situ(_Mycnd());
|
|
}
|
|
|
|
~condition_variable_any() noexcept {
|
|
_Cnd_destroy_in_situ(_Mycnd());
|
|
}
|
|
|
|
condition_variable_any(const condition_variable_any&) = delete;
|
|
condition_variable_any& operator=(const condition_variable_any&) = delete;
|
|
|
|
void notify_one() noexcept { // wake up one waiter
|
|
lock_guard<mutex> _Guard{*_Myptr};
|
|
_Cnd_signal(_Mycnd());
|
|
}
|
|
|
|
void notify_all() noexcept { // wake up all waiters
|
|
lock_guard<mutex> _Guard{*_Myptr};
|
|
_Cnd_broadcast(_Mycnd());
|
|
}
|
|
|
|
template <class _Lock>
|
|
void wait(_Lock& _Lck) noexcept /* terminates */ { // wait for signal
|
|
const shared_ptr<mutex> _Ptr = _Myptr; // for immunity to *this destruction
|
|
unique_lock<mutex> _Guard{*_Ptr};
|
|
_Unlock_guard<_Lock> _Unlock_outer{_Lck};
|
|
_Cnd_wait(_Mycnd(), _Ptr->_Mymtx());
|
|
_Guard.unlock();
|
|
} // relock _Lck
|
|
|
|
template <class _Lock, class _Predicate>
|
|
void wait(_Lock& _Lck, _Predicate _Pred) noexcept(noexcept(static_cast<bool>(_Pred()))) /* strengthened */ {
|
|
// wait for signal and check predicate
|
|
while (!static_cast<bool>(_Pred())) {
|
|
wait(_Lck);
|
|
}
|
|
}
|
|
|
|
template <class _Lock, class _Clock, class _Duration>
|
|
cv_status wait_until(_Lock& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time) {
|
|
// wait until time point
|
|
#if _HAS_CXX20
|
|
static_assert(chrono::is_clock_v<_Clock>, "Clock type required");
|
|
#endif // _HAS_CXX20
|
|
return wait_for(_Lck, _Abs_time - _Clock::now());
|
|
}
|
|
|
|
template <class _Lock, class _Clock, class _Duration, class _Predicate>
|
|
bool wait_until(_Lock& _Lck, const chrono::time_point<_Clock, _Duration>& _Abs_time, _Predicate _Pred) {
|
|
// wait for signal with timeout and check predicate
|
|
#if _HAS_CXX20
|
|
static_assert(chrono::is_clock_v<_Clock>, "Clock type required");
|
|
#endif // _HAS_CXX20
|
|
while (!_Pred()) {
|
|
if (wait_until(_Lck, _Abs_time) == cv_status::timeout) {
|
|
return _Pred();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class _Lock, class _Rep, class _Period>
|
|
cv_status wait_for(_Lock& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time) { // wait for duration
|
|
if (_Rel_time <= chrono::duration<_Rep, _Period>::zero()) {
|
|
_Unlock_guard<_Lock> _Unlock_outer{_Lck};
|
|
(void) _Unlock_outer;
|
|
return cv_status::timeout;
|
|
}
|
|
|
|
// TRANSITION, ABI: The standard says that we should use a steady clock,
|
|
// but unfortunately our ABI speaks struct xtime, which is relative to the system clock.
|
|
_CSTD xtime _Tgt;
|
|
const bool _Clamped = _To_xtime_10_day_clamped(_Tgt, _Rel_time);
|
|
const cv_status _Result = _Wait_until(_Lck, &_Tgt);
|
|
if (_Clamped) {
|
|
return cv_status::no_timeout;
|
|
}
|
|
|
|
return _Result;
|
|
}
|
|
|
|
template <class _Lock, class _Rep, class _Period, class _Predicate>
|
|
bool wait_for(_Lock& _Lck, const chrono::duration<_Rep, _Period>& _Rel_time, _Predicate _Pred) {
|
|
// wait for signal with timeout and check predicate
|
|
return wait_until(_Lck, _To_absolute_time(_Rel_time), _STD move(_Pred));
|
|
}
|
|
|
|
template <class _Lock>
|
|
cv_status wait_until(_Lock& _Lck, const xtime* const _Abs_time) { // wait for signal with timeout
|
|
return _Wait_until(_Lck, _Abs_time);
|
|
}
|
|
|
|
template <class _Lock, class _Predicate>
|
|
bool wait_until(_Lock& _Lck, const xtime* const _Abs_time, _Predicate _Pred) {
|
|
// wait for signal with timeout and check predicate
|
|
while (!_Pred()) {
|
|
if (_Wait_until(_Lck, _Abs_time) == cv_status::timeout) {
|
|
return _Pred();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if _HAS_CXX20
|
|
private:
|
|
struct _Cv_any_notify_all {
|
|
condition_variable_any* _This;
|
|
|
|
explicit _Cv_any_notify_all(condition_variable_any* _This_) : _This{_This_} {}
|
|
|
|
_Cv_any_notify_all(const _Cv_any_notify_all&) = delete;
|
|
_Cv_any_notify_all& operator=(const _Cv_any_notify_all&) = delete;
|
|
|
|
void operator()() const noexcept {
|
|
_This->notify_all();
|
|
}
|
|
};
|
|
|
|
public:
|
|
template <class _Lock, class _Predicate>
|
|
bool wait(_Lock& _Lck, stop_token _Stoken, _Predicate _Pred) noexcept(
|
|
noexcept(static_cast<bool>(_Pred()))) /* strengthened */ {
|
|
// TRANSITION, ABI: Due to the unsynchronized delivery of notify_all by _Stoken,
|
|
// this implementation cannot tolerate *this destruction while an interruptible wait
|
|
// is outstanding. A future ABI should store both the internal CV and internal mutex
|
|
// in the reference counted block to allow this.
|
|
stop_callback<_Cv_any_notify_all> _Cb{_Stoken, this};
|
|
for (;;) {
|
|
if (_Pred()) {
|
|
return true;
|
|
}
|
|
|
|
unique_lock<mutex> _Guard{*_Myptr};
|
|
if (_Stoken.stop_requested()) {
|
|
_Guard.unlock();
|
|
return _Pred();
|
|
}
|
|
|
|
_Unlock_guard<_Lock> _Unlock_outer{_Lck};
|
|
_Cnd_wait(_Mycnd(), _Myptr->_Mymtx());
|
|
_Guard.unlock();
|
|
} // relock
|
|
}
|
|
|
|
template <class _Lock, class _Clock, class _Duration, class _Predicate>
|
|
bool wait_until(
|
|
_Lock& _Lck, stop_token _Stoken, const chrono::time_point<_Clock, _Duration>& _Abs_time, _Predicate _Pred) {
|
|
static_assert(chrono::is_clock_v<_Clock>, "Clock type required");
|
|
stop_callback<_Cv_any_notify_all> _Cb{_Stoken, this};
|
|
for (;;) {
|
|
if (_Pred()) {
|
|
return true;
|
|
}
|
|
|
|
unique_lock<mutex> _Guard{*_Myptr};
|
|
if (_Stoken.stop_requested()) {
|
|
break;
|
|
}
|
|
|
|
_Unlock_guard<_Lock> _Unlock_outer{_Lck};
|
|
const auto _Now = _Clock::now();
|
|
if (_Now >= _Abs_time) {
|
|
break;
|
|
}
|
|
|
|
const auto _Rel_time = _Abs_time - _Now;
|
|
// TRANSITION, ABI: The standard says that we should use a steady clock,
|
|
// but unfortunately our ABI speaks struct xtime, which is relative to the system clock.
|
|
_CSTD xtime _Tgt;
|
|
(void) _To_xtime_10_day_clamped(_Tgt, _Rel_time);
|
|
const int _Res = _Cnd_timedwait(_Mycnd(), _Myptr->_Mymtx(), &_Tgt);
|
|
_Guard.unlock();
|
|
|
|
switch (_Res) {
|
|
case _Thrd_timedout:
|
|
case _Thrd_success:
|
|
break;
|
|
default:
|
|
_Throw_C_error(_Res);
|
|
}
|
|
} // relock
|
|
|
|
return _Pred();
|
|
}
|
|
|
|
template <class _Lock, class _Rep, class _Period, class _Predicate>
|
|
bool wait_for(_Lock& _Lck, stop_token _Stoken, const chrono::duration<_Rep, _Period>& _Rel_time, _Predicate _Pred) {
|
|
return wait_until(_Lck, _STD move(_Stoken), _To_absolute_time(_Rel_time), _STD move(_Pred));
|
|
}
|
|
#endif // _HAS_CXX20
|
|
|
|
private:
|
|
shared_ptr<mutex> _Myptr;
|
|
|
|
aligned_storage_t<_Cnd_internal_imp_size, _Cnd_internal_imp_alignment> _Cnd_storage;
|
|
|
|
_NODISCARD _Cnd_t _Mycnd() noexcept { // get pointer to _Cnd_internal_imp_t inside _Cnd_storage
|
|
return reinterpret_cast<_Cnd_t>(&_Cnd_storage);
|
|
}
|
|
|
|
template <class _Lock>
|
|
cv_status _Wait_until(_Lock& _Lck, const xtime* const _Abs_time) { // wait for signal with timeout
|
|
const shared_ptr<mutex> _Ptr = _Myptr; // for immunity to *this destruction
|
|
unique_lock<mutex> _Guard{*_Ptr};
|
|
_Unlock_guard<_Lock> _Unlock_outer{_Lck};
|
|
const int _Res = _Cnd_timedwait(_Mycnd(), _Ptr->_Mymtx(), _Abs_time);
|
|
_Guard.unlock();
|
|
|
|
switch (_Res) {
|
|
case _Thrd_success:
|
|
return cv_status::no_timeout;
|
|
case _Thrd_timedout:
|
|
return cv_status::timeout;
|
|
default:
|
|
_Throw_C_error(_Res);
|
|
}
|
|
}
|
|
};
|
|
|
|
inline void notify_all_at_thread_exit(condition_variable& _Cnd, unique_lock<mutex> _Lck) {
|
|
// register _Cnd for release at thread exit
|
|
_Cnd._Register(_Lck, nullptr);
|
|
}
|
|
_STD_END
|
|
#pragma pop_macro("new")
|
|
_STL_RESTORE_CLANG_WARNINGS
|
|
#pragma warning(pop)
|
|
#pragma pack(pop)
|
|
#endif // _STL_COMPILER_PREPROCESSOR
|
|
#endif // _CONDITION_VARIABLE_
|