MinGW: Update mingw-std-threads

Remove requirement for MinGW-specific includes
This commit is contained in:
Jonathan G Rennison
2023-12-13 20:29:05 +00:00
parent 916676998f
commit ba7d2c92d2
10 changed files with 375 additions and 140 deletions

View File

@@ -282,6 +282,10 @@ endif()
set_target_properties(openttd PROPERTIES OUTPUT_NAME "${BINARY_NAME}") set_target_properties(openttd PROPERTIES OUTPUT_NAME "${BINARY_NAME}")
# All other files are added via target_sources() # All other files are added via target_sources()
if (MINGW)
target_link_libraries(${OPENTTD_LIB} mingw_stdthreads)
endif()
set(host_tools_list strgen settingsgen) set(host_tools_list strgen settingsgen)
if(HOST_BINARY_DIR) if(HOST_BINARY_DIR)

View File

@@ -1,7 +1,24 @@
add_files( add_files(
mingw.condition_variable.h mingw.condition_variable.h
mingw.invoke.h
mingw.mutex.h mingw.mutex.h
mingw.shared_mutex.h mingw.shared_mutex.h
mingw.thread.h mingw.thread.h
CONDITION MINGW CONDITION MINGW
) )
if (MINGW)
string(CONCAT mingw_stdthreads_dir_docstring
"Optional. When generating std-like headers , this variable can be set"
"to manually specify the path to mingw-stdthreads directory containing"
"original library headers.")
set(MINGW_STDTHREADS_DIR "${CMAKE_CURRENT_SOURCE_DIR}"
CACHE PATH ${mingw_stdthreads_dir_docstring})
# mingw-stdthreads is a header-only library, so make it a INTERFACE target
add_library(mingw_stdthreads INTERFACE)
target_include_directories(mingw_stdthreads INTERFACE "${PROJECT_SOURCE_DIR}")
add_subdirectory(cmake_stdheaders_generator)
target_link_libraries(mingw_stdthreads INTERFACE cmake_stdheaders_generator)
endif()

View File

@@ -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 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.
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.

View File

@@ -0,0 +1,78 @@
cmake_minimum_required(VERSION 3.0)
project(cmake_stdheaders_generator)
set(output_include_path "${PROJECT_BINARY_DIR}/${PROJECT_NAME}")
message("${PROJECT_NAME}: output_include_path set to ${output_include_path}")
function(generate_mingw_stdthreads_header header_file_name
mingw_stdthreads_folder)
set(template_file_path "${PROJECT_SOURCE_DIR}/template.cpp")
set(destination_file_path "${output_include_path}/${header_file_name}")
# Check if compiler is gcc or clang
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
# Actually this should never happen because it should have already
# been checked in the parent CMakeLists.txt
message(FATAL_ERROR "Unsupported compiler")
endif()
# Call g++ to retrieve header path
# The -H option will let g++ outputs header dependencies to stderr, and the
# content of stderr is saved to variable compiler_output.
try_compile(unused_compiler_exit_code ${CMAKE_CURRENT_BINARY_DIR}
"${template_file_path}"
COMPILE_DEFINITIONS
-H "-DMINGW_STDTHREADS_DETECTING_SYSTEM_HEADER=<${header_file_name}>"
OUTPUT_VARIABLE compiler_output)
# Get full path to system header
string(REGEX MATCH "[.] ([^\r\n]*)" _ "${compiler_output}")
set(mingw_stdthreads_headers_generator_system_header "${CMAKE_MATCH_1}")
message("Matched: <${mingw_stdthreads_headers_generator_system_header}>")
# Ensure file exists
if(NOT EXISTS "${mingw_stdthreads_headers_generator_system_header}")
message(FATAL_ERROR "<${header_file_name}>'s path not found, "
"compiler output was:\n${compiler_output}")
endif()
# Get full path to mingw-stdthreads header
set(mingw_stdthreads_headers_generator_library_header
"${mingw_stdthreads_folder}/mingw.${header_file_name}.h")
# Normalize paths
file(TO_CMAKE_PATH "${mingw_stdthreads_headers_generator_system_header}"
mingw_stdthreads_headers_generator_system_header)
file(TO_CMAKE_PATH "${mingw_stdthreads_headers_generator_library_header}"
mingw_stdthreads_headers_generator_library_header)
configure_file("${template_file_path}" "${destination_file_path}")
endfunction()
if(EXISTS "${MINGW_STDTHREADS_DIR}")
message("${PROJECT_NAME}: MINGW_STDTHREADS_DIR: "
"${MINGW_STDTHREADS_DIR}")
else()
message(FATAL_ERROR "${PROECT_NAME}: MINGW_STDTHREADS_DIR does not "
"exist: ${MINGW_STDTHREADS_DIR}")
endif()
# <condition_variable>
generate_mingw_stdthreads_header(condition_variable "${MINGW_STDTHREADS_DIR}")
# <future>
#generate_mingw_stdthreads_header(future "${MINGW_STDTHREADS_DIR}")
# <latch>
#generate_mingw_stdthreads_header(latch "${MINGW_STDTHREADS_DIR}")
# <mutex>
generate_mingw_stdthreads_header(mutex "${MINGW_STDTHREADS_DIR}")
# <shared_mutex>
generate_mingw_stdthreads_header(shared_mutex "${MINGW_STDTHREADS_DIR}")
# <thread>
generate_mingw_stdthreads_header(thread "${MINGW_STDTHREADS_DIR}")
# the generated headers are to be considered as a header only library
# so we create an interface target
add_library(${PROJECT_NAME} INTERFACE)
target_compile_definitions(${PROJECT_NAME} INTERFACE
MINGW_STDTHREADS_GENERATED_STDHEADERS)
target_include_directories(${PROJECT_NAME} INTERFACE "${output_include_path}")

View File

@@ -0,0 +1,11 @@
#ifdef MINGW_STDTHREADS_DETECTING_SYSTEM_HEADER
#include MINGW_STDTHREADS_DETECTING_SYSTEM_HEADER
static_assert(false, "Prevent compilation")
#else
#pragma once
// both system header and mignw-stdthreads header should already have include
// guards. But we still add a #pragma once just to be safe.
#include "${mingw_stdthreads_headers_generator_system_header}"
#include "${mingw_stdthreads_headers_generator_library_header}"
#endif

View File

@@ -30,11 +30,24 @@
//#include <cassert> //#include <cassert>
#include <chrono> #include <chrono>
#include <system_error> #include <system_error>
#include <windows.h>
#include <sdkddkver.h> // Detect Windows version.
#if (WINVER < _WIN32_WINNT_VISTA) #if (WINVER < _WIN32_WINNT_VISTA)
#include <atomic> #include <atomic>
#endif #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.mutex.h"
#include "mingw.shared_mutex.h" #include "mingw.shared_mutex.h"
@@ -71,7 +84,7 @@ public:
condition_variable_any(const condition_variable_any&) = delete; condition_variable_any(const condition_variable_any&) = delete;
condition_variable_any& operator=(const condition_variable_any&) = delete; condition_variable_any& operator=(const condition_variable_any&) = delete;
condition_variable_any() condition_variable_any()
: mSemaphore(CreateSemaphore(NULL, 0, 0xFFFF, NULL)) : mSemaphore(CreateSemaphoreA(NULL, 0, 0xFFFF, NULL))
{ {
if (mSemaphore == NULL) if (mSemaphore == NULL)
throw std::system_error(GetLastError(), std::generic_category()); throw std::system_error(GetLastError(), std::generic_category());
@@ -259,6 +272,7 @@ namespace vista
// If compiling for Vista or higher, use the native condition variable. // If compiling for Vista or higher, use the native condition variable.
class condition_variable class condition_variable
{ {
static constexpr DWORD kInfinite = 0xffffffffl;
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
CONDITION_VARIABLE cvariable_ = CONDITION_VARIABLE_INIT; CONDITION_VARIABLE cvariable_ = CONDITION_VARIABLE_INIT;
@@ -345,7 +359,7 @@ public:
void wait (unique_lock<mutex> & lock) void wait (unique_lock<mutex> & lock)
{ {
wait_impl(lock, INFINITE); wait_impl(lock, kInfinite);
} }
template<class Predicate> template<class Predicate>
@@ -361,8 +375,8 @@ public:
{ {
using namespace std::chrono; using namespace std::chrono;
auto timeout = duration_cast<milliseconds>(rel_time).count(); auto timeout = duration_cast<milliseconds>(rel_time).count();
DWORD waittime = (timeout < INFINITE) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (INFINITE - 1); DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (kInfinite - 1);
bool result = wait_impl(lock, waittime) || (timeout >= INFINITE); bool result = wait_impl(lock, waittime) || (timeout >= kInfinite);
return result ? cv_status::no_timeout : cv_status::timeout; return result ? cv_status::no_timeout : cv_status::timeout;
} }
@@ -399,6 +413,7 @@ public:
class condition_variable_any class condition_variable_any
{ {
static constexpr DWORD kInfinite = 0xffffffffl;
using native_shared_mutex = windows7::shared_mutex; using native_shared_mutex = windows7::shared_mutex;
condition_variable internal_cv_ {}; condition_variable internal_cv_ {};
@@ -465,7 +480,7 @@ public:
template<class L> template<class L>
void wait (L & lock) void wait (L & lock)
{ {
wait_impl(lock, INFINITE); wait_impl(lock, kInfinite);
} }
template<class L, class Predicate> template<class L, class Predicate>
@@ -480,8 +495,8 @@ public:
{ {
using namespace std::chrono; using namespace std::chrono;
auto timeout = duration_cast<milliseconds>(period).count(); auto timeout = duration_cast<milliseconds>(period).count();
DWORD waittime = (timeout < INFINITE) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (INFINITE - 1); DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (kInfinite - 1);
bool result = wait_impl(lock, waittime) || (timeout >= INFINITE); bool result = wait_impl(lock, waittime) || (timeout >= kInfinite);
return result ? cv_status::no_timeout : cv_status::timeout; return result ? cv_status::no_timeout : cv_status::timeout;
} }

View File

@@ -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 <type_traits> // For std::result_of, etc.
#include <utility> // For std::forward
#include <functional> // For std::reference_wrapper
namespace mingw_stdthread
{
namespace detail
{
// For compatibility, implement std::invoke for C++11 and C++14
#if __cplusplus < 201703L
template<bool PMemFunc, bool PMemData>
struct Invoker
{
template<class F, class... Args>
inline static typename std::result_of<F(Args...)>::type invoke (F&& f, Args&&... args)
{
return std::forward<F>(f)(std::forward<Args>(args)...);
}
};
template<bool>
struct InvokerHelper;
template<>
struct InvokerHelper<false>
{
template<class T1>
inline static auto get (T1&& t1) -> decltype(*std::forward<T1>(t1))
{
return *std::forward<T1>(t1);
}
template<class T1>
inline static auto get (const std::reference_wrapper<T1>& t1) -> decltype(t1.get())
{
return t1.get();
}
};
template<>
struct InvokerHelper<true>
{
template<class T1>
inline static auto get (T1&& t1) -> decltype(std::forward<T1>(t1))
{
return std::forward<T1>(t1);
}
};
template<>
struct Invoker<true, false>
{
template<class T, class F, class T1, class... Args>
inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\
decltype((InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...))
{
return (InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
}
};
template<>
struct Invoker<false, true>
{
template<class T, class F, class T1, class... Args>
inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\
decltype(InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f)
{
return InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f;
}
};
template<class F, class... Args>
struct InvokeResult
{
typedef Invoker<std::is_member_function_pointer<typename std::remove_reference<F>::type>::value,
std::is_member_object_pointer<typename std::remove_reference<F>::type>::value &&
(sizeof...(Args) == 1)> invoker;
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>
auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...))
{
return InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
#else
using std::invoke;
#endif
} // Namespace "detail"
} // Namespace "mingw_stdthread"
#endif

View File

@@ -43,10 +43,24 @@
#include <cstdio> #include <cstdio>
#endif #endif
#include <windows.h> #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 // Need for the implementation of invoke
#include "mingw.thread.h" #include "mingw.invoke.h"
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0501) #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. #error To use the MinGW-std-threads library, you will need to define the macro _WIN32_WINNT to be 0x0501 (Windows XP) or higher.
@@ -144,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
{ {
@@ -291,18 +313,21 @@ using xp::mutex;
class recursive_timed_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 inline bool try_lock_internal (DWORD ms) noexcept
{ {
DWORD ret = WaitForSingleObject(mHandle, ms); DWORD ret = WaitForSingleObject(mHandle, ms);
#ifndef NDEBUG #ifndef NDEBUG
if (ret == WAIT_ABANDONED) if (ret == kWaitAbandoned)
{ {
using namespace std; using namespace std;
fprintf(stderr, "FATAL: Thread terminated while holding a mutex."); fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate(); terminate();
} }
#endif #endif
return (ret == WAIT_OBJECT_0) || (ret == WAIT_ABANDONED); return (ret == kWaitObject0) || (ret == kWaitAbandoned);
} }
protected: protected:
HANDLE mHandle; HANDLE mHandle;
@@ -325,19 +350,19 @@ public:
} }
void lock() void lock()
{ {
DWORD ret = WaitForSingleObject(mHandle, INFINITE); DWORD ret = WaitForSingleObject(mHandle, kInfinite);
// If (ret == WAIT_ABANDONED), then the thread that held ownership was // If (ret == WAIT_ABANDONED), then the thread that held ownership was
// terminated. Behavior is undefined, but Windows will pass ownership to this // terminated. Behavior is undefined, but Windows will pass ownership to this
// thread. // thread.
#ifndef NDEBUG #ifndef NDEBUG
if (ret == WAIT_ABANDONED) if (ret == kWaitAbandoned)
{ {
using namespace std; using namespace std;
fprintf(stderr, "FATAL: Thread terminated while holding a mutex."); fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate(); terminate();
} }
#endif #endif
if ((ret != WAIT_OBJECT_0) && (ret != WAIT_ABANDONED)) if ((ret != kWaitObject0) && (ret != kWaitAbandoned))
{ {
throw std::system_error(GetLastError(), std::system_category()); throw std::system_error(GetLastError(), std::system_category());
} }
@@ -358,7 +383,7 @@ public:
auto timeout = duration_cast<milliseconds>(dur).count(); auto timeout = duration_cast<milliseconds>(dur).count();
while (timeout > 0) while (timeout > 0)
{ {
constexpr auto kMaxStep = static_cast<decltype(timeout)>(INFINITE-1); constexpr auto kMaxStep = static_cast<decltype(timeout)>(kInfinite-1);
auto step = (timeout < kMaxStep) ? timeout : kMaxStep; auto step = (timeout < kMaxStep) ? timeout : kMaxStep;
if (try_lock_internal(static_cast<DWORD>(step))) if (try_lock_internal(static_cast<DWORD>(step)))
return true; return true;
@@ -378,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()
@@ -439,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);

View File

@@ -51,11 +51,19 @@
// For defer_lock_t, adopt_lock_t, and try_to_lock_t // For defer_lock_t, adopt_lock_t, and try_to_lock_t
#include "mingw.mutex.h" #include "mingw.mutex.h"
// For this_thread::yield. // For this_thread::yield.
#include "mingw.thread.h" //#include "mingw.thread.h"
// 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 <windows.h> #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 #endif
namespace mingw_stdthread namespace mingw_stdthread
@@ -97,8 +105,6 @@ public:
if (expected >= kWriteBit - 1) if (expected >= kWriteBit - 1)
{ {
using namespace std; using namespace std;
using namespace this_thread;
yield();
expected = mCounter.load(std::memory_order_relaxed); expected = mCounter.load(std::memory_order_relaxed);
continue; continue;
} }
@@ -144,12 +150,12 @@ public:
// Might be able to use relaxed memory order... // Might be able to use relaxed memory order...
// Wait for the write-lock to be unlocked, then claim the write slot. // Wait for the write-lock to be unlocked, then claim the write slot.
counter_type current; counter_type current;
while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit) while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit);
this_thread::yield(); //this_thread::yield();
// Wait for readers to finish up. // Wait for readers to finish up.
while (current != kWriteBit) while (current != kWriteBit)
{ {
this_thread::yield(); //this_thread::yield();
current = mCounter.load(std::memory_order_acquire); current = mCounter.load(std::memory_order_acquire);
} }
#if STDMUTEX_RECURSION_CHECKS #if STDMUTEX_RECURSION_CHECKS

View File

@@ -35,15 +35,22 @@
#include <tuple> // For std::tuple #include <tuple> // For std::tuple
#include <chrono> // For sleep timing. #include <chrono> // For sleep timing.
#include <memory> // For std::unique_ptr #include <memory> // For std::unique_ptr
#include <ostream> // Stream output for thread ids. #include <iosfwd> // Stream output for thread ids.
#include <utility> // For std::swap, std::forward #include <utility> // For std::swap, std::forward
// For the invoke implementation only: #include "mingw.invoke.h"
#include <type_traits> // For std::result_of, etc.
//#include <utility> // For std::forward
//#include <functional> // For std::reference_wrapper
#include <windows.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 #include <process.h> // For _beginthreadex
#ifndef NDEBUG #ifndef NDEBUG
@@ -59,89 +66,6 @@ namespace mingw_stdthread
{ {
namespace detail namespace detail
{ {
// For compatibility, implement std::invoke for C++11 and C++14
#if __cplusplus < 201703L
template<bool PMemFunc, bool PMemData>
struct Invoker
{
template<class F, class... Args>
inline static typename std::result_of<F(Args...)>::type invoke (F&& f, Args&&... args)
{
return std::forward<F>(f)(std::forward<Args>(args)...);
}
};
template<bool>
struct InvokerHelper;
template<>
struct InvokerHelper<false>
{
template<class T1>
inline static auto get (T1&& t1) -> decltype(*std::forward<T1>(t1))
{
return *std::forward<T1>(t1);
}
template<class T1>
inline static auto get (const std::reference_wrapper<T1>& t1) -> decltype(t1.get())
{
return t1.get();
}
};
template<>
struct InvokerHelper<true>
{
template<class T1>
inline static auto get (T1&& t1) -> decltype(std::forward<T1>(t1))
{
return std::forward<T1>(t1);
}
};
template<>
struct Invoker<true, false>
{
template<class T, class F, class T1, class... Args>
inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\
decltype((InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...))
{
return (InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
}
};
template<>
struct Invoker<false, true>
{
template<class T, class F, class T1, class... Args>
inline static auto invoke (F T::* f, T1&& t1, Args&&... args) ->\
decltype(InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f)
{
return InvokerHelper<std::is_base_of<T,typename std::decay<T1>::type>::value>::get(t1).*f;
}
};
template<class F, class... Args>
struct InvokeResult
{
typedef Invoker<std::is_member_function_pointer<typename std::remove_reference<F>::type>::value,
std::is_member_object_pointer<typename std::remove_reference<F>::type>::value &&
(sizeof...(Args) == 1)> invoker;
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>
auto invoke (F&& f, Args&&... args) -> decltype(InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...))
{
return InvokeResult<F, Args...>::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
#else
using std::invoke;
#endif
template<std::size_t...> template<std::size_t...>
struct IntSeq {}; struct IntSeq {};
@@ -151,29 +75,34 @@ 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 // Use a template specialization to avoid relying on compiler optimization
template<class Func, typename... Args> // when determining the parameter integer sequence.
class ThreadFuncCall 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...>
{ {
typedef std::tuple<Args...> Tuple; static_assert(sizeof...(S) == sizeof...(Args), "Args must match.");
Func mFunc; using Tuple = std::tuple<typename std::decay<Args>::type...>;
typename std::decay<Func>::type mFunc;
Tuple mArgs; Tuple mArgs;
template <std::size_t... S>
void callFunc(detail::IntSeq<S...>)
{
detail::invoke(std::forward<Func>(mFunc), std::get<S>(std::forward<Tuple>(mArgs)) ...);
}
public: public:
ThreadFuncCall(Func&& aFunc, Args&&... aArgs) ThreadFuncCall(Func&& aFunc, Args&&... aArgs)
:mFunc(std::forward<Func>(aFunc)), mArgs(std::forward<Args>(aArgs)...){} : mFunc(std::forward<Func>(aFunc)),
mArgs(std::forward<Args>(aArgs)...)
{
}
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
@@ -181,12 +110,13 @@ class thread
public: public:
class id class id
{ {
DWORD mId; DWORD mId = 0;
void clear() {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; }
@@ -210,6 +140,7 @@ public:
}; };
private: private:
static constexpr HANDLE kInvalidHandle = nullptr; static constexpr HANDLE kInvalidHandle = nullptr;
static constexpr DWORD kInfinite = 0xffffffffl;
HANDLE mHandle; HANDLE mHandle;
id mThreadId; id mThreadId;
@@ -244,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;
@@ -252,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, static_cast<LPVOID>(call), 0, &id_receiver);
reinterpret_cast<unsigned*>(&(mThreadId.mId)));
if (int_handle == 0) if (int_handle == 0)
{ {
mHandle = kInvalidHandle; mHandle = kInvalidHandle;
@@ -265,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;}
@@ -284,10 +218,10 @@ public:
throw system_error(make_error_code(errc::no_such_process)); throw system_error(make_error_code(errc::no_such_process));
if (!joinable()) if (!joinable())
throw system_error(make_error_code(errc::invalid_argument)); throw system_error(make_error_code(errc::invalid_argument));
WaitForSingleObject(mHandle, INFINITE); WaitForSingleObject(mHandle, kInfinite);
CloseHandle(mHandle); CloseHandle(mHandle);
mHandle = kInvalidHandle; mHandle = kInvalidHandle;
mThreadId.clear(); mThreadId = id{};
} }
~thread() ~thread()
@@ -339,23 +273,39 @@ 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)
{ {
static constexpr DWORD kInfinite = 0xffffffffl;
using namespace std::chrono; using namespace std::chrono;
using rep = milliseconds::rep; using rep = milliseconds::rep;
rep ms = duration_cast<milliseconds>(sleep_duration).count(); rep ms = duration_cast<milliseconds>(sleep_duration).count();
while (ms > 0) while (ms > 0)
{ {
constexpr rep kMaxRep = static_cast<rep>(INFINITE - 1); constexpr rep kMaxRep = static_cast<rep>(kInfinite - 1);
auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep; auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep;
Sleep(static_cast<DWORD>(sleepTime)); Sleep(static_cast<DWORD>(sleepTime));
ms -= sleepTime; ms -= sleepTime;