@@ -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>`. | |||
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 | |||
------------- | |||
@@ -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 | |||
----------------------------------------------------- | |||
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. | |||
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. | |||
@@ -3,4 +3,7 @@ | |||
#pragma once | |||
#include_next <condition_variable> | |||
#if __GNUC__ < 12 | |||
#include "mingw.condition_variable.h" | |||
#endif |
@@ -3,4 +3,7 @@ | |||
#pragma once | |||
#include_next <future> | |||
#if __GNUC__ < 12 | |||
#include "mingw.future.h" | |||
#endif |
@@ -3,4 +3,7 @@ | |||
#pragma once | |||
#include_next <invoke> | |||
#if __GNUC__ < 12 | |||
#include "mingw.invoke.h" | |||
#endif |
@@ -34,11 +34,20 @@ | |||
#include <sdkddkver.h> // Detect Windows version. | |||
#if (WINVER < _WIN32_WINNT_VISTA) | |||
#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 <winbase.h> // For CreateSemaphore | |||
#include <handleapi.h> | |||
#endif | |||
#include <synchapi.h> | |||
#endif | |||
#include "mingw.mutex.h" | |||
#include "mingw.shared_mutex.h" | |||
@@ -123,7 +132,7 @@ private: | |||
else | |||
{ | |||
using namespace std; | |||
throw system_error(make_error_code((errc)EPROTO)); | |||
throw system_error(make_error_code(errc::protocol_error)); | |||
} | |||
} | |||
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)...)) | |||
{ | |||
return invoker::invoke(std::forward<F>(f), std::forward<Args>(args)...); | |||
}; | |||
} | |||
}; | |||
template<class F, class...Args> | |||
@@ -43,14 +43,21 @@ | |||
#include <cstdio> | |||
#endif | |||
#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 | |||
#include <processthreadsapi.h> // For GetCurrentThreadId | |||
#endif | |||
#include <synchapi.h> // For InitializeCriticalSection, etc. | |||
#include <errhandlingapi.h> // For GetLastError | |||
#include <handleapi.h> | |||
#endif | |||
// Need for the implementation of invoke | |||
#include "mingw.invoke.h" | |||
@@ -151,6 +158,14 @@ struct _OwnerThread | |||
// Though the Slim Reader-Writer (SRW) locks used here are not complete until | |||
// Windows 7, implementing partial functionality in Vista will simplify the | |||
// 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) | |||
namespace windows7 | |||
{ | |||
@@ -388,6 +403,7 @@ public: | |||
class timed_mutex: recursive_timed_mutex | |||
{ | |||
public: | |||
timed_mutex() = default; | |||
timed_mutex(const timed_mutex&) = delete; | |||
timed_mutex& operator=(const timed_mutex&) = delete; | |||
void lock() | |||
@@ -449,7 +465,7 @@ void call_once(once_flag& flag, Callable&& func, Args&&... args) | |||
if (flag.mHasRun.load(std::memory_order_acquire)) | |||
return; | |||
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; | |||
detail::invoke(std::forward<Callable>(func),std::forward<Args>(args)...); | |||
flag.mHasRun.store(true, std::memory_order_release); | |||
@@ -56,8 +56,15 @@ | |||
// Might be able to use native Slim Reader-Writer (SRW) locks. | |||
#ifdef _WIN32 | |||
#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> | |||
#endif | |||
#endif | |||
namespace mingw_stdthread | |||
{ | |||
@@ -40,10 +40,17 @@ | |||
#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 <handleapi.h> // For CloseHandle, etc. | |||
#include <sysinfoapi.h> // For GetNativeSystemInfo | |||
#include <processthreadsapi.h> // For GetCurrentThreadId | |||
#endif | |||
#include <process.h> // For _beginthreadex | |||
#ifndef NDEBUG | |||
@@ -68,20 +75,19 @@ namespace detail | |||
template<std::size_t... S> | |||
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...>; | |||
typename std::decay<Func>::type mFunc; | |||
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: | |||
ThreadFuncCall(Func&& aFunc, Args&&... aArgs) | |||
: mFunc(std::forward<Func>(aFunc)), | |||
@@ -91,10 +97,12 @@ namespace detail | |||
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" | |||
class thread | |||
@@ -102,12 +110,13 @@ class thread | |||
public: | |||
class id | |||
{ | |||
DWORD mId; | |||
void clear() {mId = 0;} | |||
DWORD mId = 0; | |||
friend class thread; | |||
friend class std::hash<id>; | |||
friend class detail::ThreadIdTool; | |||
explicit id(DWORD aId) noexcept : mId(aId){} | |||
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; } | |||
@@ -166,7 +175,7 @@ public: | |||
:mHandle(other.mHandle), mThreadId(other.mThreadId) | |||
{ | |||
other.mHandle = kInvalidHandle; | |||
other.mThreadId.clear(); | |||
other.mThreadId = id{}; | |||
} | |||
thread(const thread &other)=delete; | |||
@@ -174,12 +183,13 @@ public: | |||
template<class Func, typename... Args> | |||
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( | |||
std::forward<Func>(func), std::forward<Args>(args)...); | |||
unsigned id_receiver; | |||
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) | |||
{ | |||
mHandle = kInvalidHandle; | |||
@@ -187,8 +197,10 @@ public: | |||
delete call; | |||
// Note: Should only throw EINVAL, EAGAIN, EACCES | |||
throw std::system_error(errnum, std::generic_category()); | |||
} else | |||
} else { | |||
mThreadId.mId = id_receiver; | |||
mHandle = reinterpret_cast<HANDLE>(int_handle); | |||
} | |||
} | |||
bool joinable() const {return mHandle != kInvalidHandle;} | |||
@@ -209,7 +221,7 @@ public: | |||
WaitForSingleObject(mHandle, kInfinite); | |||
CloseHandle(mHandle); | |||
mHandle = kInvalidHandle; | |||
mThreadId.clear(); | |||
mThreadId = id{}; | |||
} | |||
~thread() | |||
@@ -261,13 +273,28 @@ moving another thread to it.\n"); | |||
CloseHandle(mHandle); | |||
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 | |||
{ | |||
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);} | |||
template< class Rep, class Period > | |||
void sleep_for( const std::chrono::duration<Rep,Period>& sleep_duration) | |||
@@ -3,4 +3,7 @@ | |||
#pragma once | |||
#include_next <mutex> | |||
#if __GNUC__ < 12 | |||
#include "mingw.mutex.h" | |||
#endif |
@@ -3,4 +3,7 @@ | |||
#pragma once | |||
#include_next <shared_mutex> | |||
#if __GNUC__ < 12 | |||
#include "mingw.shared_mutex.h" | |||
#endif |
@@ -3,4 +3,7 @@ | |||
#pragma once | |||
#include_next <thread> | |||
#if __GNUC__ < 12 | |||
#include "mingw.thread.h" | |||
#endif |