/** * std::thread implementation for MinGW-w64 * * Copyright (c) 2013-2016 by Mega Limited, Auckland, New Zealand * Copyright (c) 2022 the build2 authors * * Licensed under the simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #ifndef LIBBUTL_MINGW_THREAD_HXX #define LIBBUTL_MINGW_THREAD_HXX #if !defined(__cplusplus) || (__cplusplus < 201402L) # error C++14 compiler required #endif #if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0601 # error _WIN32_WINNT should be 0x0601 (Windows 7) or greater #endif #include // For std::size_t #include // Detect error type. #include // For std::terminate #include // For std::system_error #include // For std::hash, std::invoke (C++17) #include // For std::tuple #include // For sleep timing. #include // For std::unique_ptr #include // Stream output for thread ids. #include // For std::swap, std::forward #include // For WaitForSingleObject #include // For CloseHandle, etc. #include // For GetNativeSystemInfo #include // For GetCurrentThreadId #include // For _beginthreadex #if __cplusplus < 201703L # include #endif namespace mingw_stdthread { // @@ I think can get rid of this in C++14. // namespace detail { template struct IntSeq {}; template struct GenIntSeq : GenIntSeq { }; template struct GenIntSeq<0, S...> { typedef IntSeq type; }; // Use a template specialization to avoid relying on compiler optimization // when determining the parameter integer sequence. template class ThreadFuncCall; // We can't define the Call struct in the function - the standard forbids template methods in that case template class ThreadFuncCall, Args...> { static_assert(sizeof...(S) == sizeof...(Args), "Args must match."); using Tuple = std::tuple::type...>; typename std::decay::type mFunc; Tuple mArgs; public: ThreadFuncCall(Func&& aFunc, Args&&... aArgs) : mFunc(std::forward(aFunc)), mArgs(std::forward(aArgs)...) { } void callFunc() { #if __cplusplus < 201703L detail::invoke(std::move(mFunc), std::move(std::get(mArgs)) ...); #else std::invoke (std::move(mFunc), std::move(std::get(mArgs)) ...); #endif } }; // Allow construction of threads without exposing implementation. class ThreadIdTool; } class thread { public: class id { DWORD mId = 0; friend class thread; friend class std::hash; friend class detail::ThreadIdTool; explicit id(DWORD aId) noexcept : mId(aId){} public: id () noexcept = default; friend bool operator==(id x, id y) noexcept {return x.mId == y.mId; } friend bool operator!=(id x, id y) noexcept {return x.mId != y.mId; } friend bool operator< (id x, id y) noexcept {return x.mId < y.mId; } friend bool operator<=(id x, id y) noexcept {return x.mId <= y.mId; } friend bool operator> (id x, id y) noexcept {return x.mId > y.mId; } friend bool operator>=(id x, id y) noexcept {return x.mId >= y.mId; } template friend std::basic_ostream<_CharT, _Traits>& operator<<(std::basic_ostream<_CharT, _Traits>& __out, id __id) { if (__id.mId == 0) { return __out << ""; } else { return __out << __id.mId; } } }; private: static constexpr HANDLE kInvalidHandle = nullptr; static constexpr DWORD kInfinite = 0xffffffffl; HANDLE mHandle; id mThreadId; template static unsigned __stdcall threadfunc(void* arg) { std::unique_ptr call(static_cast(arg)); call->callFunc(); return 0; } static unsigned int _hardware_concurrency_helper() noexcept { SYSTEM_INFO sysinfo; ::GetNativeSystemInfo(&sysinfo); return sysinfo.dwNumberOfProcessors; } public: typedef HANDLE native_handle_type; id get_id() const noexcept {return mThreadId;} native_handle_type native_handle() const {return mHandle;} thread(): mHandle(kInvalidHandle), mThreadId(){} thread(thread&& other) noexcept :mHandle(other.mHandle), mThreadId(other.mThreadId) { other.mHandle = kInvalidHandle; other.mThreadId = id{}; } thread(const thread &other) = delete; template explicit thread(Func&& func, Args&&... args) : mHandle(), mThreadId() { // Instead of INVALID_HANDLE_VALUE, _beginthreadex returns 0. using ArgSequence = typename detail::GenIntSeq::type; using Call = detail::ThreadFuncCall; auto call = new Call(std::forward(func), std::forward(args)...); unsigned int id_receiver; auto int_handle = _beginthreadex(NULL, 0, threadfunc, static_cast(call), 0, &id_receiver); if (int_handle == 0) { mHandle = kInvalidHandle; int errnum = errno; delete call; // Note: Should only throw EINVAL, EAGAIN, EACCES throw std::system_error(errnum, std::generic_category()); } else { mThreadId.mId = id_receiver; mHandle = reinterpret_cast(int_handle); } } bool joinable() const {return mHandle != kInvalidHandle;} // Note: Due to lack of synchronization, this function has a race // condition if called concurrently, which leads to undefined // behavior. The same applies to all other member functions of this // class, but this one is mentioned explicitly. void join() { using namespace std; if (get_id() == id(GetCurrentThreadId())) throw system_error(make_error_code(errc::resource_deadlock_would_occur)); if (mHandle == kInvalidHandle) throw system_error(make_error_code(errc::no_such_process)); if (!joinable()) throw system_error(make_error_code(errc::invalid_argument)); WaitForSingleObject(mHandle, kInfinite); CloseHandle(mHandle); mHandle = kInvalidHandle; mThreadId = id{}; } ~thread() { if (joinable()) { // @@ TODO /* #ifndef NDEBUG std::printf("Error: Must join() or detach() a thread before \ destroying it.\n"); #endif */ std::terminate(); } } thread& operator=(const thread&) = delete; thread& operator=(thread&& other) noexcept { if (joinable()) { // @@ TODO /* #ifndef NDEBUG std::printf("Error: Must join() or detach() a thread before \ moving another thread to it.\n"); #endif */ std::terminate(); } swap(other); return *this; } void swap(thread& other) noexcept { std::swap(mHandle, other.mHandle); std::swap(mThreadId.mId, other.mThreadId.mId); } static unsigned int hardware_concurrency() noexcept { // @@ TODO: this seems like a bad idea. // /*static*/ unsigned int cached = _hardware_concurrency_helper(); return cached; } void detach() { if (!joinable()) { using namespace std; throw system_error(make_error_code(errc::invalid_argument)); } if (mHandle != kInvalidHandle) { CloseHandle(mHandle); mHandle = kInvalidHandle; } mThreadId = id{}; } }; namespace detail { class ThreadIdTool { public: static thread::id make_id (DWORD base_id) noexcept { return thread::id(base_id); } }; } namespace this_thread { inline thread::id get_id() noexcept { return detail::ThreadIdTool::make_id(GetCurrentThreadId()); } inline void yield() noexcept {Sleep(0);} 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(kInfinite - 1); auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep; Sleep(static_cast(sleepTime)); ms -= sleepTime; } } template void sleep_until(const std::chrono::time_point& sleep_time) { sleep_for(sleep_time-Clock::now()); } } } namespace std { // Specialize hash for this implementation's thread::id, even if the // std::thread::id already has a hash. template<> struct hash { typedef mingw_stdthread::thread::id argument_type; typedef size_t result_type; size_t operator() (const argument_type & i) const noexcept { return i.mId; } }; } #endif // LIBBUTL_MINGW_THREAD_HXX