@@ -1 +1 @@ | |||||
Subproject commit 3b826a31c4127df82e23cfd12c2dd14dc77a042d | |||||
Subproject commit 176f819c681405a12e7f6ecc1e2e1af472c3ed7b |
@@ -18,6 +18,23 @@ This is a header-only library. To use, just include the corresponding `mingw.xxx | |||||
For example, `#include "mingw.thread.h"` replaces `#include <thread>`. | For example, `#include "mingw.thread.h"` replaces `#include <thread>`. | ||||
A `CMakeLists.txt` has also been provided. You can add it to your project by using `add_subdirectory()`, and then this library can be added as your targets' dependency by using `target_link_libraries(YOUR_TARGET PRIVATE mingw_stdthreads)`. By default it just adds a include path, allowing you to include headers using angle brackets (for example `#include <mingw.thread.h>`). But you can also provide options to let it generate "std-like" headers (see next paragraph). | |||||
Using "std-like" headers | |||||
------------------------ | |||||
Probably you don't really want to replace all your includes from `#include <header>` to `#include "mingw.header.h"`. So if you are using GCC or clang, here are some ways to make you happy :) | |||||
With CMake, you just need to turn on the option `MINGW_STDTHREADS_GENERATE_STDHEADERS` before adding mingw-stdthreads, something like this: | |||||
```CMake | |||||
option(MINGW_STDTHREADS_GENERATE_STDHEADERS "" ON) | |||||
add_subdirectory(mingw_stdthreads) | |||||
target_link_libraries(${TARGET} PRIVATE mingw_stdthreads) | |||||
``` | |||||
When CMake generates project files, headers named in the "standard header" way will be generated and added to your include path. Then you can avoid stuffs like `mingw.thread.h`, and keep using `#include <thread>` like always. In addition, `MINGW_STDTHREADS_GENERATED_STDHEADERS` will be defined, you can use this macro to check if those generated headers are actually available. | |||||
If you aren't using CMake, you can use one of the three scripts inside [utility_scripts](utility_scripts) directory to manually generate those "std-like" headers. Note that this requires Microsoft Power Shell, so if you are cross-compiling, you would need to install Power Shell. | |||||
Compatibility | Compatibility | ||||
------------- | ------------- | ||||
@@ -25,8 +42,10 @@ This code has been tested to work with MinGW-w64 5.3.0, but should work with any | |||||
Switching from the win32-pthread based implementation | Switching from the win32-pthread based implementation | ||||
----------------------------------------------------- | ----------------------------------------------------- | ||||
It seems that recent versions of MinGW-w64 include a Win32 port of pthreads, and have the `std::thread`, `std::mutex`, etc. classes implemented and working based on that compatibility | |||||
layer. | |||||
It seems that recent versions of MinGW-w64 include a Win32 port of pthreads, and have the `std::thread`, `std::mutex`, etc. classes implemented and working based on that compatibility layer. | |||||
You could use the built-in pthread implementation of Mingw by using the posix compiler, eg: `x86_64-w64-mingw32-g++-posix` (for Windows 64-bit). | |||||
That is a somewhat heavier implementation, as it relies on an abstraction layer, so you may still want to use this implementation for efficiency purposes. | That is a somewhat heavier implementation, as it relies on an abstraction layer, so you may still want to use this implementation for efficiency purposes. | ||||
Unfortunately you can't use this library standalone and independent of the system `<mutex>` headers, as it relies on those headers for `std::unique_lock` and other non-trivial utility classes. | Unfortunately you can't use this library standalone and independent of the system `<mutex>` headers, as it relies on those headers for `std::unique_lock` and other non-trivial utility classes. | ||||
In that case you will need to edit the `c++-config.h` file of your MinGW setup and comment out the definition of _GLIBCXX_HAS_GTHREADS. | In that case you will need to edit the `c++-config.h` file of your MinGW setup and comment out the definition of _GLIBCXX_HAS_GTHREADS. | ||||
@@ -3,4 +3,7 @@ | |||||
#pragma once | #pragma once | ||||
#include_next <condition_variable> | #include_next <condition_variable> | ||||
#if __GNUC__ < 12 | |||||
#include "mingw.condition_variable.h" | #include "mingw.condition_variable.h" | ||||
#endif |
@@ -3,4 +3,7 @@ | |||||
#pragma once | #pragma once | ||||
#include_next <future> | #include_next <future> | ||||
#if __GNUC__ < 12 | |||||
#include "mingw.future.h" | #include "mingw.future.h" | ||||
#endif |
@@ -3,4 +3,7 @@ | |||||
#pragma once | #pragma once | ||||
#include_next <invoke> | #include_next <invoke> | ||||
#if __GNUC__ < 12 | |||||
#include "mingw.invoke.h" | #include "mingw.invoke.h" | ||||
#endif |
@@ -34,11 +34,20 @@ | |||||
#include <sdkddkver.h> // Detect Windows version. | #include <sdkddkver.h> // Detect Windows version. | ||||
#if (WINVER < _WIN32_WINNT_VISTA) | #if (WINVER < _WIN32_WINNT_VISTA) | ||||
#include <atomic> | #include <atomic> | ||||
#endif | |||||
#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) | |||||
#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\ | |||||
with Microsoft's API. We'll try to work around this, but we can make no\ | |||||
guarantees. This problem does not exist in MinGW-w64." | |||||
#include <windows.h> // No further granularity can be expected. | |||||
#else | |||||
#if (WINVER < _WIN32_WINNT_VISTA) | |||||
#include <windef.h> | #include <windef.h> | ||||
#include <winbase.h> // For CreateSemaphore | #include <winbase.h> // For CreateSemaphore | ||||
#include <handleapi.h> | #include <handleapi.h> | ||||
#endif | #endif | ||||
#include <synchapi.h> | #include <synchapi.h> | ||||
#endif | |||||
#include "mingw.mutex.h" | #include "mingw.mutex.h" | ||||
#include "mingw.shared_mutex.h" | #include "mingw.shared_mutex.h" | ||||
@@ -123,7 +132,7 @@ private: | |||||
else | else | ||||
{ | { | ||||
using namespace std; | using namespace std; | ||||
throw system_error(make_error_code((errc)EPROTO)); | |||||
throw system_error(make_error_code(errc::protocol_error)); | |||||
} | } | ||||
} | } | ||||
public: | public: | ||||
@@ -92,7 +92,7 @@ namespace detail | |||||
inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...)) | inline static auto invoke (F&& f, Args&&... args) -> decltype(invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...)) | ||||
{ | { | ||||
return invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...); | return invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...); | ||||
}; | |||||
} | |||||
}; | }; | ||||
template<class F, class...Args> | template<class F, class...Args> | ||||
@@ -43,14 +43,21 @@ | |||||
#include <cstdio> | #include <cstdio> | ||||
#endif | #endif | ||||
#include <sdkddkver.h> // Detect Windows version. | #include <sdkddkver.h> // Detect Windows version. | ||||
#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) | |||||
#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\ | |||||
with Microsoft's API. We'll try to work around this, but we can make no\ | |||||
guarantees. This problem does not exist in MinGW-w64." | |||||
#include <windows.h> // No further granularity can be expected. | |||||
#else | |||||
#if STDMUTEX_RECURSION_CHECKS | #if STDMUTEX_RECURSION_CHECKS | ||||
#include <processthreadsapi.h> // For GetCurrentThreadId | #include <processthreadsapi.h> // For GetCurrentThreadId | ||||
#endif | #endif | ||||
#include <synchapi.h> // For InitializeCriticalSection, etc. | #include <synchapi.h> // For InitializeCriticalSection, etc. | ||||
#include <errhandlingapi.h> // For GetLastError | #include <errhandlingapi.h> // For GetLastError | ||||
#include <handleapi.h> | #include <handleapi.h> | ||||
#endif | |||||
// Need for the implementation of invoke | // Need for the implementation of invoke | ||||
#include "mingw.invoke.h" | #include "mingw.invoke.h" | ||||
@@ -151,6 +158,14 @@ struct _OwnerThread | |||||
// Though the Slim Reader-Writer (SRW) locks used here are not complete until | // Though the Slim Reader-Writer (SRW) locks used here are not complete until | ||||
// Windows 7, implementing partial functionality in Vista will simplify the | // Windows 7, implementing partial functionality in Vista will simplify the | ||||
// interaction with condition variables. | // interaction with condition variables. | ||||
//Define SRWLOCK_INIT. | |||||
#if !defined(SRWLOCK_INIT) | |||||
#pragma message "SRWLOCK_INIT macro is not defined. Defining automatically." | |||||
#define SRWLOCK_INIT {0} | |||||
#endif | |||||
#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA) | #if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA) | ||||
namespace windows7 | namespace windows7 | ||||
{ | { | ||||
@@ -388,6 +403,7 @@ public: | |||||
class timed_mutex: recursive_timed_mutex | class timed_mutex: recursive_timed_mutex | ||||
{ | { | ||||
public: | public: | ||||
timed_mutex() = default; | |||||
timed_mutex(const timed_mutex&) = delete; | timed_mutex(const timed_mutex&) = delete; | ||||
timed_mutex& operator=(const timed_mutex&) = delete; | timed_mutex& operator=(const timed_mutex&) = delete; | ||||
void lock() | void lock() | ||||
@@ -449,7 +465,7 @@ void call_once(once_flag& flag, Callable&& func, Args&&... args) | |||||
if (flag.mHasRun.load(std::memory_order_acquire)) | if (flag.mHasRun.load(std::memory_order_acquire)) | ||||
return; | return; | ||||
lock_guard<decltype(flag.mMutex)> lock(flag.mMutex); | lock_guard<decltype(flag.mMutex)> lock(flag.mMutex); | ||||
if (flag.mHasRun.load(std::memory_order_acquire)) | |||||
if (flag.mHasRun.load(std::memory_order_relaxed)) | |||||
return; | return; | ||||
detail::invoke(std::forward<Callable>(func),std::forward<Args>(args)...); | detail::invoke(std::forward<Callable>(func),std::forward<Args>(args)...); | ||||
flag.mHasRun.store(true, std::memory_order_release); | flag.mHasRun.store(true, std::memory_order_release); | ||||
@@ -56,8 +56,15 @@ | |||||
// Might be able to use native Slim Reader-Writer (SRW) locks. | // Might be able to use native Slim Reader-Writer (SRW) locks. | ||||
#ifdef _WIN32 | #ifdef _WIN32 | ||||
#include <sdkddkver.h> // Detect Windows version. | #include <sdkddkver.h> // Detect Windows version. | ||||
#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) | |||||
#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\ | |||||
with Microsoft's API. We'll try to work around this, but we can make no\ | |||||
guarantees. This problem does not exist in MinGW-w64." | |||||
#include <windows.h> // No further granularity can be expected. | |||||
#else | |||||
#include <synchapi.h> | #include <synchapi.h> | ||||
#endif | #endif | ||||
#endif | |||||
namespace mingw_stdthread | namespace mingw_stdthread | ||||
{ | { | ||||
@@ -40,10 +40,17 @@ | |||||
#include "mingw.invoke.h" | #include "mingw.invoke.h" | ||||
#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) | |||||
#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\ | |||||
with Microsoft's API. We'll try to work around this, but we can make no\ | |||||
guarantees. This problem does not exist in MinGW-w64." | |||||
#include <windows.h> // No further granularity can be expected. | |||||
#else | |||||
#include <synchapi.h> // For WaitForSingleObject | #include <synchapi.h> // For WaitForSingleObject | ||||
#include <handleapi.h> // For CloseHandle, etc. | #include <handleapi.h> // For CloseHandle, etc. | ||||
#include <sysinfoapi.h> // For GetNativeSystemInfo | #include <sysinfoapi.h> // For GetNativeSystemInfo | ||||
#include <processthreadsapi.h> // For GetCurrentThreadId | #include <processthreadsapi.h> // For GetCurrentThreadId | ||||
#endif | |||||
#include <process.h> // For _beginthreadex | #include <process.h> // For _beginthreadex | ||||
#ifndef NDEBUG | #ifndef NDEBUG | ||||
@@ -68,20 +75,19 @@ namespace detail | |||||
template<std::size_t... S> | template<std::size_t... S> | ||||
struct GenIntSeq<0, S...> { typedef IntSeq<S...> type; }; | struct GenIntSeq<0, S...> { typedef IntSeq<S...> type; }; | ||||
// We can't define the Call struct in the function - the standard forbids template methods in that case | |||||
template<class Func, typename... Args> | |||||
class ThreadFuncCall | |||||
// Use a template specialization to avoid relying on compiler optimization | |||||
// when determining the parameter integer sequence. | |||||
template<class Func, class T, typename... Args> | |||||
class ThreadFuncCall; | |||||
// We can't define the Call struct in the function - the standard forbids template methods in that case | |||||
template<class Func, std::size_t... S, typename... Args> | |||||
class ThreadFuncCall<Func, detail::IntSeq<S...>, Args...> | |||||
{ | { | ||||
static_assert(sizeof...(S) == sizeof...(Args), "Args must match."); | |||||
using Tuple = std::tuple<typename std::decay<Args>::type...>; | using Tuple = std::tuple<typename std::decay<Args>::type...>; | ||||
typename std::decay<Func>::type mFunc; | typename std::decay<Func>::type mFunc; | ||||
Tuple mArgs; | Tuple mArgs; | ||||
template <std::size_t... S> | |||||
void callFunc(detail::IntSeq<S...>) | |||||
{ | |||||
// Note: Only called once (per thread) | |||||
detail::invoke(std::move(mFunc), std::move(std::get<S>(mArgs)) ...); | |||||
} | |||||
public: | public: | ||||
ThreadFuncCall(Func&& aFunc, Args&&... aArgs) | ThreadFuncCall(Func&& aFunc, Args&&... aArgs) | ||||
: mFunc(std::forward<Func>(aFunc)), | : mFunc(std::forward<Func>(aFunc)), | ||||
@@ -91,10 +97,12 @@ namespace detail | |||||
void callFunc() | void callFunc() | ||||
{ | { | ||||
callFunc(typename detail::GenIntSeq<sizeof...(Args)>::type()); | |||||
detail::invoke(std::move(mFunc), std::move(std::get<S>(mArgs)) ...); | |||||
} | } | ||||
}; | }; | ||||
// Allow construction of threads without exposing implementation. | |||||
class ThreadIdTool; | |||||
} // Namespace "detail" | } // Namespace "detail" | ||||
class thread | class thread | ||||
@@ -102,12 +110,13 @@ class thread | |||||
public: | public: | ||||
class id | class id | ||||
{ | { | ||||
DWORD mId; | |||||
void clear() {mId = 0;} | |||||
DWORD mId = 0; | |||||
friend class thread; | friend class thread; | ||||
friend class std::hash<id>; | friend class std::hash<id>; | ||||
friend class detail::ThreadIdTool; | |||||
explicit id(DWORD aId) noexcept : mId(aId){} | |||||
public: | public: | ||||
explicit id(DWORD aId=0) noexcept : mId(aId){} | |||||
id (void) 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; } | ||||
@@ -166,7 +175,7 @@ public: | |||||
:mHandle(other.mHandle), mThreadId(other.mThreadId) | :mHandle(other.mHandle), mThreadId(other.mThreadId) | ||||
{ | { | ||||
other.mHandle = kInvalidHandle; | other.mHandle = kInvalidHandle; | ||||
other.mThreadId.clear(); | |||||
other.mThreadId = id{}; | |||||
} | } | ||||
thread(const thread &other)=delete; | thread(const thread &other)=delete; | ||||
@@ -174,12 +183,13 @@ public: | |||||
template<class Func, typename... Args> | template<class Func, typename... Args> | ||||
explicit thread(Func&& func, Args&&... args) : mHandle(), mThreadId() | explicit thread(Func&& func, Args&&... args) : mHandle(), mThreadId() | ||||
{ | { | ||||
typedef detail::ThreadFuncCall<Func, Args...> Call; | |||||
using ArgSequence = typename detail::GenIntSeq<sizeof...(Args)>::type; | |||||
using Call = detail::ThreadFuncCall<Func, ArgSequence, Args...>; | |||||
auto call = new Call( | auto call = new Call( | ||||
std::forward<Func>(func), std::forward<Args>(args)...); | std::forward<Func>(func), std::forward<Args>(args)...); | ||||
unsigned id_receiver; | |||||
auto int_handle = _beginthreadex(NULL, 0, threadfunc<Call>, | auto int_handle = _beginthreadex(NULL, 0, threadfunc<Call>, | ||||
static_cast<LPVOID>(call), 0, | |||||
reinterpret_cast<unsigned*>(&(mThreadId.mId))); | |||||
static_cast<LPVOID>(call), 0, &id_receiver); | |||||
if (int_handle == 0) | if (int_handle == 0) | ||||
{ | { | ||||
mHandle = kInvalidHandle; | mHandle = kInvalidHandle; | ||||
@@ -187,8 +197,10 @@ public: | |||||
delete call; | delete call; | ||||
// Note: Should only throw EINVAL, EAGAIN, EACCES | // Note: Should only throw EINVAL, EAGAIN, EACCES | ||||
throw std::system_error(errnum, std::generic_category()); | throw std::system_error(errnum, std::generic_category()); | ||||
} else | |||||
} else { | |||||
mThreadId.mId = id_receiver; | |||||
mHandle = reinterpret_cast<HANDLE>(int_handle); | mHandle = reinterpret_cast<HANDLE>(int_handle); | ||||
} | |||||
} | } | ||||
bool joinable() const {return mHandle != kInvalidHandle;} | bool joinable() const {return mHandle != kInvalidHandle;} | ||||
@@ -209,7 +221,7 @@ public: | |||||
WaitForSingleObject(mHandle, kInfinite); | WaitForSingleObject(mHandle, kInfinite); | ||||
CloseHandle(mHandle); | CloseHandle(mHandle); | ||||
mHandle = kInvalidHandle; | mHandle = kInvalidHandle; | ||||
mThreadId.clear(); | |||||
mThreadId = id{}; | |||||
} | } | ||||
~thread() | ~thread() | ||||
@@ -261,13 +273,28 @@ moving another thread to it.\n"); | |||||
CloseHandle(mHandle); | CloseHandle(mHandle); | ||||
mHandle = kInvalidHandle; | mHandle = kInvalidHandle; | ||||
} | } | ||||
mThreadId.clear(); | |||||
mThreadId = id{}; | |||||
} | } | ||||
}; | }; | ||||
namespace detail | |||||
{ | |||||
class ThreadIdTool | |||||
{ | |||||
public: | |||||
static thread::id make_id (DWORD base_id) noexcept | |||||
{ | |||||
return thread::id(base_id); | |||||
} | |||||
}; | |||||
} // Namespace "detail" | |||||
namespace this_thread | namespace this_thread | ||||
{ | { | ||||
inline thread::id get_id() noexcept {return thread::id(GetCurrentThreadId());} | |||||
inline thread::id get_id() noexcept | |||||
{ | |||||
return detail::ThreadIdTool::make_id(GetCurrentThreadId()); | |||||
} | |||||
inline void yield() noexcept {Sleep(0);} | inline void yield() noexcept {Sleep(0);} | ||||
template< class Rep, class Period > | template< class Rep, class Period > | ||||
void sleep_for( const std::chrono::duration<Rep,Period>& sleep_duration) | void sleep_for( const std::chrono::duration<Rep,Period>& sleep_duration) | ||||
@@ -3,4 +3,7 @@ | |||||
#pragma once | #pragma once | ||||
#include_next <mutex> | #include_next <mutex> | ||||
#if __GNUC__ < 12 | |||||
#include "mingw.mutex.h" | #include "mingw.mutex.h" | ||||
#endif |
@@ -3,4 +3,7 @@ | |||||
#pragma once | #pragma once | ||||
#include_next <shared_mutex> | #include_next <shared_mutex> | ||||
#if __GNUC__ < 12 | |||||
#include "mingw.shared_mutex.h" | #include "mingw.shared_mutex.h" | ||||
#endif |
@@ -3,4 +3,7 @@ | |||||
#pragma once | #pragma once | ||||
#include_next <thread> | #include_next <thread> | ||||
#if __GNUC__ < 12 | |||||
#include "mingw.thread.h" | #include "mingw.thread.h" | ||||
#endif |