From f3e966f84d8604242406e210b358a211f0a0c248 Mon Sep 17 00:00:00 2001 From: "Nathaniel J. McClatchey, PhD" Date: Fri, 16 Aug 2019 21:55:41 -0700 Subject: [PATCH 1/3] Reduce to minimal Windows headers And change to to minimize include tree. Note: The macro/constant "INFINITE" is replaced with its value. --- mingw.condition_variable.h | 22 ++++++++++++++-------- mingw.future.h | 7 ++++++- mingw.mutex.h | 23 ++++++++++++++++------- mingw.shared_mutex.h | 13 +++++++------ mingw.thread.h | 13 +++++++++---- 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/mingw.condition_variable.h b/mingw.condition_variable.h index 9862dc5..e8ccbd3 100644 --- a/mingw.condition_variable.h +++ b/mingw.condition_variable.h @@ -30,11 +30,15 @@ #include #include #include -#include +#include // Detect Windows version. #if (WINVER < _WIN32_WINNT_VISTA) #include +#include +#include // For CreateSemaphore +#include #endif +#include #include "mingw.mutex.h" #include "mingw.shared_mutex.h" @@ -71,7 +75,7 @@ class condition_variable_any condition_variable_any(const condition_variable_any&) = delete; condition_variable_any& operator=(const condition_variable_any&) = delete; condition_variable_any() - : mSemaphore(CreateSemaphore(NULL, 0, 0xFFFF, NULL)) + : mSemaphore(CreateSemaphoreA(NULL, 0, 0xFFFF, NULL)) { if (mSemaphore == NULL) throw std::system_error(GetLastError(), std::generic_category()); @@ -259,6 +263,7 @@ namespace vista // If compiling for Vista or higher, use the native condition variable. class condition_variable { + static constexpr DWORD kInfinite = 0xffffffffl; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" CONDITION_VARIABLE cvariable_ = CONDITION_VARIABLE_INIT; @@ -345,7 +350,7 @@ use native Win32 critical section objects."); void wait (unique_lock & lock) { - wait_impl(lock, INFINITE); + wait_impl(lock, kInfinite); } template @@ -361,8 +366,8 @@ use native Win32 critical section objects."); { using namespace std::chrono; auto timeout = duration_cast(rel_time).count(); - DWORD waittime = (timeout < INFINITE) ? ((timeout < 0) ? 0 : static_cast(timeout)) : (INFINITE - 1); - bool result = wait_impl(lock, waittime) || (timeout >= INFINITE); + DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast(timeout)) : (kInfinite - 1); + bool result = wait_impl(lock, waittime) || (timeout >= kInfinite); return result ? cv_status::no_timeout : cv_status::timeout; } @@ -399,6 +404,7 @@ use native Win32 critical section objects."); class condition_variable_any { + static constexpr DWORD kInfinite = 0xffffffffl; using native_shared_mutex = windows7::shared_mutex; condition_variable internal_cv_ {}; @@ -465,7 +471,7 @@ class condition_variable_any template void wait (L & lock) { - wait_impl(lock, INFINITE); + wait_impl(lock, kInfinite); } template @@ -480,8 +486,8 @@ class condition_variable_any { using namespace std::chrono; auto timeout = duration_cast(period).count(); - DWORD waittime = (timeout < INFINITE) ? ((timeout < 0) ? 0 : static_cast(timeout)) : (INFINITE - 1); - bool result = wait_impl(lock, waittime) || (timeout >= INFINITE); + DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast(timeout)) : (kInfinite - 1); + bool result = wait_impl(lock, waittime) || (timeout >= kInfinite); return result ? cv_status::no_timeout : cv_status::timeout; } diff --git a/mingw.future.h b/mingw.future.h index 3a86055..47d50cf 100644 --- a/mingw.future.h +++ b/mingw.future.h @@ -34,6 +34,10 @@ #include "mingw.mutex.h" #include "mingw.condition_variable.h" +#include +#include +#include + // Note: // std::shared_ptr is the natural choice for this. However, a custom // implementation removes the need to keep a control block separate from the @@ -484,6 +488,7 @@ class promise : mingw_stdthread::detail::FutureBase // Need OS support for this... void make_ready_at_thread_exit (void) { + static constexpr DWORD kInfinite = 0xffffffffl; // Need to turn the pseudohandle from GetCurrentThread() into a true handle... HANDLE thread_handle; BOOL success = DuplicateHandle(GetCurrentProcess(), @@ -508,7 +513,7 @@ class promise : mingw_stdthread::detail::FutureBase } ptr->get_condition_variable().notify_all(); // Wait for the original thread to die. - WaitForSingleObject(thread_handle, INFINITE); + WaitForSingleObject(thread_handle, kInfinite); CloseHandle(thread_handle); { diff --git a/mingw.mutex.h b/mingw.mutex.h index b97314c..b691098 100644 --- a/mingw.mutex.h +++ b/mingw.mutex.h @@ -43,7 +43,13 @@ #include #endif -#include + +#include // Detect Windows version. +#if STDMUTEX_RECURSION_CHECKS +#include // For GetCurrentThreadId +#endif +#include // For InitializeCriticalSection, etc. +#include // For GetLastError // Need for the implementation of invoke #include "mingw.thread.h" @@ -291,18 +297,21 @@ using xp::mutex; class recursive_timed_mutex { + static constexpr DWORD kWaitAbandoned = 0x00000080l; + static constexpr DWORD kWaitObject0 = 0x00000000l; + static constexpr DWORD kInfinite = 0xffffffffl; inline bool try_lock_internal (DWORD ms) noexcept { DWORD ret = WaitForSingleObject(mHandle, ms); #ifndef NDEBUG - if (ret == WAIT_ABANDONED) + if (ret == kWaitAbandoned) { using namespace std; fprintf(stderr, "FATAL: Thread terminated while holding a mutex."); terminate(); } #endif - return (ret == WAIT_OBJECT_0) || (ret == WAIT_ABANDONED); + return (ret == kWaitObject0) || (ret == kWaitAbandoned); } protected: HANDLE mHandle; @@ -325,19 +334,19 @@ class recursive_timed_mutex } void lock() { - DWORD ret = WaitForSingleObject(mHandle, INFINITE); + DWORD ret = WaitForSingleObject(mHandle, kInfinite); // If (ret == WAIT_ABANDONED), then the thread that held ownership was // terminated. Behavior is undefined, but Windows will pass ownership to this // thread. #ifndef NDEBUG - if (ret == WAIT_ABANDONED) + if (ret == kWaitAbandoned) { using namespace std; fprintf(stderr, "FATAL: Thread terminated while holding a mutex."); terminate(); } #endif - if ((ret != WAIT_OBJECT_0) && (ret != WAIT_ABANDONED)) + if ((ret != kWaitObject0) && (ret != kWaitAbandoned)) { throw std::system_error(GetLastError(), std::system_category()); } @@ -358,7 +367,7 @@ class recursive_timed_mutex auto timeout = duration_cast(dur).count(); while (timeout > 0) { - constexpr auto kMaxStep = static_cast(INFINITE-1); + constexpr auto kMaxStep = static_cast(kInfinite-1); auto step = (timeout < kMaxStep) ? timeout : kMaxStep; if (try_lock_internal(static_cast(step))) return true; diff --git a/mingw.shared_mutex.h b/mingw.shared_mutex.h index 72cc09a..a3b86ec 100644 --- a/mingw.shared_mutex.h +++ b/mingw.shared_mutex.h @@ -51,11 +51,12 @@ // For defer_lock_t, adopt_lock_t, and try_to_lock_t #include "mingw.mutex.h" // For this_thread::yield. -#include "mingw.thread.h" +//#include "mingw.thread.h" // Might be able to use native Slim Reader-Writer (SRW) locks. #ifdef _WIN32 -#include +#include // Detect Windows version. +#include #endif namespace mingw_stdthread @@ -98,7 +99,7 @@ class shared_mutex { using namespace std; using namespace this_thread; - yield(); + //yield(); expected = mCounter.load(std::memory_order_relaxed); continue; } @@ -144,12 +145,12 @@ class shared_mutex // Might be able to use relaxed memory order... // Wait for the write-lock to be unlocked, then claim the write slot. counter_type current; - while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit) - this_thread::yield(); + while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit); + //this_thread::yield(); // Wait for readers to finish up. while (current != kWriteBit) { - this_thread::yield(); + //this_thread::yield(); current = mCounter.load(std::memory_order_acquire); } #if STDMUTEX_RECURSION_CHECKS diff --git a/mingw.thread.h b/mingw.thread.h index 2d01f28..e38a9dc 100644 --- a/mingw.thread.h +++ b/mingw.thread.h @@ -35,7 +35,7 @@ #include // For std::tuple #include // For sleep timing. #include // For std::unique_ptr -#include // Stream output for thread ids. +#include // Stream output for thread ids. #include // For std::swap, std::forward // For the invoke implementation only: @@ -43,7 +43,10 @@ //#include // For std::forward //#include // For std::reference_wrapper -#include +#include // For WaitForSingleObject +#include // For CloseHandle, etc. +#include // For GetNativeSystemInfo +#include // For GetCurrentThreadId #include // For _beginthreadex #ifndef NDEBUG @@ -210,6 +213,7 @@ class thread }; private: static constexpr HANDLE kInvalidHandle = nullptr; + static constexpr DWORD kInfinite = 0xffffffffl; HANDLE mHandle; id mThreadId; @@ -284,7 +288,7 @@ class thread throw system_error(make_error_code(errc::no_such_process)); if (!joinable()) throw system_error(make_error_code(errc::invalid_argument)); - WaitForSingleObject(mHandle, INFINITE); + WaitForSingleObject(mHandle, kInfinite); CloseHandle(mHandle); mHandle = kInvalidHandle; mThreadId.clear(); @@ -350,12 +354,13 @@ namespace this_thread template< class Rep, class Period > void sleep_for( const std::chrono::duration& sleep_duration) { + static constexpr DWORD kInfinite = 0xffffffffl; using namespace std::chrono; using rep = milliseconds::rep; rep ms = duration_cast(sleep_duration).count(); while (ms > 0) { - constexpr rep kMaxRep = static_cast(INFINITE - 1); + constexpr rep kMaxRep = static_cast(kInfinite - 1); auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep; Sleep(static_cast(sleepTime)); ms -= sleepTime; From b1ac4b8118fe1cb3bf2ca0a182b7063c6b437a1c Mon Sep 17 00:00:00 2001 From: "Nathaniel J. McClatchey, PhD" Date: Fri, 16 Aug 2019 22:36:15 -0700 Subject: [PATCH 2/3] Separate the invoke implementation Further reduce interdependency by separating invoke implementation from thread implementation. --- mingw.invoke.h | 109 +++++++++++++++++++++++++++++++++++++++++++ mingw.mutex.h | 3 +- mingw.shared_mutex.h | 2 - mingw.thread.h | 88 +--------------------------------- 4 files changed, 112 insertions(+), 90 deletions(-) create mode 100644 mingw.invoke.h diff --git a/mingw.invoke.h b/mingw.invoke.h new file mode 100644 index 0000000..71a6e50 --- /dev/null +++ b/mingw.invoke.h @@ -0,0 +1,109 @@ +/// \file mingw.invoke.h +/// \brief Lightweight `invoke` implementation, for C++11 and C++14. +/// +/// (c) 2018-2019 by Nathaniel J. McClatchey, San Jose, CA, United States +/// \author Nathaniel J. McClatchey, PhD +/// +/// \copyright Simplified (2-clause) BSD License. +/// +/// \note This file may become part of the mingw-w64 runtime package. If/when +/// this happens, the appropriate license will be added, i.e. this code will +/// become dual-licensed, and the current BSD 2-clause license will stay. + +#ifndef MINGW_INVOKE_H_ +#define MINGW_INVOKE_H_ + +#include // For std::result_of, etc. +#include // For std::forward +#include // For std::reference_wrapper + +namespace mingw_stdthread +{ +namespace detail +{ +// For compatibility, implement std::invoke for C++11 and C++14 +#if __cplusplus < 201703L + template + struct Invoker + { + template + inline static typename std::result_of::type invoke (F&& f, Args&&... args) + { + return std::forward(f)(std::forward(args)...); + } + }; + template + struct InvokerHelper; + + template<> + struct InvokerHelper + { + template + inline static auto get (T1&& t1) -> decltype(*std::forward(t1)) + { + return *std::forward(t1); + } + + template + inline static auto get (const std::reference_wrapper& t1) -> decltype(t1.get()) + { + return t1.get(); + } + }; + + template<> + struct InvokerHelper + { + template + inline static auto get (T1&& t1) -> decltype(std::forward(t1)) + { + return std::forward(t1); + } + }; + + template<> + struct Invoker + { + template + inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\ + decltype((InvokerHelper::type>::value>::get(std::forward(t1)).*f)(std::forward(args)...)) + { + return (InvokerHelper::type>::value>::get(std::forward(t1)).*f)(std::forward(args)...); + } + }; + + template<> + struct Invoker + { + template + inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\ + decltype(InvokerHelper::type>::value>::get(t1).*f) + { + return InvokerHelper::type>::value>::get(t1).*f; + } + }; + + template + struct InvokeResult + { + typedef Invoker::type>::value, + std::is_member_object_pointer::type>::value && + (sizeof...(Args) == 1)> invoker; + inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward(f), std::forward(args)...)) + { + return invoker::invoke(std::forward(f), std::forward(args)...); + }; + }; + + template + auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult::invoke(std::forward(f), std::forward(args)...)) + { + return InvokeResult::invoke(std::forward(f), std::forward(args)...); + } +#else + using std::invoke; +#endif +} // Namespace "detail" +} // Namespace "mingw_stdthread" + +#endif diff --git a/mingw.mutex.h b/mingw.mutex.h index b691098..5548f16 100644 --- a/mingw.mutex.h +++ b/mingw.mutex.h @@ -50,9 +50,10 @@ #endif #include // For InitializeCriticalSection, etc. #include // For GetLastError +#include // Need for the implementation of invoke -#include "mingw.thread.h" +#include "mingw.invoke.h" #if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0501) #error To use the MinGW-std-threads library, you will need to define the macro _WIN32_WINNT to be 0x0501 (Windows XP) or higher. diff --git a/mingw.shared_mutex.h b/mingw.shared_mutex.h index a3b86ec..e498de9 100644 --- a/mingw.shared_mutex.h +++ b/mingw.shared_mutex.h @@ -98,8 +98,6 @@ class shared_mutex if (expected >= kWriteBit - 1) { using namespace std; - using namespace this_thread; - //yield(); expected = mCounter.load(std::memory_order_relaxed); continue; } diff --git a/mingw.thread.h b/mingw.thread.h index e38a9dc..caf333e 100644 --- a/mingw.thread.h +++ b/mingw.thread.h @@ -38,10 +38,7 @@ #include // Stream output for thread ids. #include // For std::swap, std::forward -// For the invoke implementation only: -#include // For std::result_of, etc. -//#include // For std::forward -//#include // For std::reference_wrapper +#include "mingw.invoke.h" #include // For WaitForSingleObject #include // For CloseHandle, etc. @@ -62,89 +59,6 @@ namespace mingw_stdthread { namespace detail { -// For compatibility, implement std::invoke for C++11 and C++14 -#if __cplusplus < 201703L - template - struct Invoker - { - template - inline static typename std::result_of::type invoke (F&& f, Args&&... args) - { - return std::forward(f)(std::forward(args)...); - } - }; - template - struct InvokerHelper; - - template<> - struct InvokerHelper - { - template - inline static auto get (T1&& t1) -> decltype(*std::forward(t1)) - { - return *std::forward(t1); - } - - template - inline static auto get (const std::reference_wrapper& t1) -> decltype(t1.get()) - { - return t1.get(); - } - }; - - template<> - struct InvokerHelper - { - template - inline static auto get (T1&& t1) -> decltype(std::forward(t1)) - { - return std::forward(t1); - } - }; - - template<> - struct Invoker - { - template - inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\ - decltype((InvokerHelper::type>::value>::get(std::forward(t1)).*f)(std::forward(args)...)) - { - return (InvokerHelper::type>::value>::get(std::forward(t1)).*f)(std::forward(args)...); - } - }; - - template<> - struct Invoker - { - template - inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\ - decltype(InvokerHelper::type>::value>::get(t1).*f) - { - return InvokerHelper::type>::value>::get(t1).*f; - } - }; - - template - struct InvokeResult - { - typedef Invoker::type>::value, - std::is_member_object_pointer::type>::value && - (sizeof...(Args) == 1)> invoker; - inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward(f), std::forward(args)...)) - { - return invoker::invoke(std::forward(f), std::forward(args)...); - }; - }; - - template - auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult::invoke(std::forward(f), std::forward(args)...)) - { - return InvokeResult::invoke(std::forward(f), std::forward(args)...); - } -#else - using std::invoke; -#endif - template struct IntSeq {}; From 2cc22915a791119919707c73a80b593c3399bb44 Mon Sep 17 00:00:00 2001 From: "Nathaniel J. McClatchey, PhD" Date: Fri, 16 Aug 2019 22:39:46 -0700 Subject: [PATCH 3/3] Better exception pointer handling. Avoid try-catch block where it is not required. --- mingw.future.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mingw.future.h b/mingw.future.h index 47d50cf..04008a9 100644 --- a/mingw.future.h +++ b/mingw.future.h @@ -477,11 +477,7 @@ class promise : mingw_stdthread::detail::FutureBase { if (valid() && !(mState->mType.load(std::memory_order_relaxed) & kSetFlag)) { - try { - throw future_error(future_errc::broken_promise); - } catch (...) { - set_exception(std::current_exception()); - } + set_exception(std::make_exception_ptr(future_error(future_errc::broken_promise))); } } /// \bug Might throw more exceptions than specified by the standard...