add algo files

This commit is contained in:
lzx
2025-07-04 17:47:19 +08:00
parent 155a338571
commit e7a707ea39
122 changed files with 18845 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
project(mingw_stdthreads)
cmake_minimum_required(VERSION 3.0)
option(MINGW_STDTHREADS_BUILD_TEST "Build tests")
option(MINGW_STDTHREADS_GENERATE_STDHEADERS "Generate std-like headers")
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 "${PROJECT_SOURCE_DIR}"
CACHE PATH ${mingw_stdthreads_dir_docstring})
# mingw-stdthreads is a header-only library, so make it a INTERFACE target
add_library(${PROJECT_NAME} INTERFACE)
target_include_directories(${PROJECT_NAME} INTERFACE "${PROJECT_SOURCE_DIR}")
if(MINGW_STDTHREADS_GENERATE_STDHEADERS)
# Check if we are using gcc or clang
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
# Add as dependency and generate std headers
add_subdirectory(cmake_stdheaders_generator)
target_link_libraries(${PROJECT_NAME} INTERFACE
cmake_stdheaders_generator)
else()
message(WARNING "Cannot generate std headers with this compiler: "
${CMAKE_CXX_COMPILER_ID} ". "
"Please fall back to #include <mingw.xxx.h>")
endif()
endif()
# Build tests.exe
if(MINGW_STDTHREADS_BUILD_TEST)
add_subdirectory(tests)
endif()

View File

@@ -0,0 +1,24 @@
Copyright (c) 2016, Mega Limited
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,58 @@
mingw-std-threads
=================
Implementation of standard C++11 threading classes, which are currently still missing on MinGW GCC.
Target Windows version
----------------------
This implementation should work with Windows XP (regardless of service pack), or newer.
The library automatically detects the version of Windows that is being targeted (at compile time), and selects an implementation that takes advantage of available Windows features.
In MinGW GCC, the target Windows version may optionally be selected by the command-line option `-D _WIN32_WINNT=...`.
Use `0x0600` for Windows Vista, or `0x0601` for Windows 7.
See "[Modifying `WINVER` and `_WIN32_WINNT`](https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt)" for more details.
Usage
-----
This is a header-only library. To use, just include the corresponding `mingw.xxx.h file`, where `xxx` would be the name of the standard header that you would normally include.
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
-------------
This code has been tested to work with MinGW-w64 5.3.0, but should work with any other MinGW version that has the `std` threading classes missing, has C++11 support for lambda functions, variadic templates, and has working mutex helper classes in `<mutex>`.
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.
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.
This will cause the system headers not to define the actual `thread`, `mutex`, etc. classes, but still define the necessary utility classes.
Why MinGW has no threading classes
----------------------------------
It seems that for cross-platform threading implementation, the GCC standard library relies on the gthreads/pthreads library.
If this library is not available, as is the case with MinGW, the classes `std::thread`, `std::mutex`, `std::condition_variable` are not defined.
However, various usable helper classes are still defined in the system headers.
Hence, this implementation does not re-define them, and instead includes those headers.

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
set(compiler_arguments
${template_file_path}
-H
"-DMINGW_STDTHREADS_DETECTING_SYSTEM_HEADER=<${header_file_name}>")
# And content of stderr is saved to variable compiler_output
execute_process(COMMAND "${CMAKE_CXX_COMPILER}" ${compiler_arguments}
ERROR_VARIABLE compiler_output
OUTPUT_QUIET)
# 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}")
# <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

@@ -0,0 +1,564 @@
/**
* @file condition_variable.h
* @brief std::condition_variable implementation for MinGW
*
* (c) 2013-2016 by Mega Limited, Auckland, New Zealand
* @author Alexander Vassilev
*
* @copyright 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.
* @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_CONDITIONAL_VARIABLE_H
#define MINGW_CONDITIONAL_VARIABLE_H
#if !defined(__cplusplus) || (__cplusplus < 201103L)
#error A C++11 compiler is required!
#endif
// Use the standard classes for std::, if available.
#include <condition_variable>
#include <cassert>
#include <chrono>
#include <system_error>
#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"
#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.
#endif
namespace mingw_stdthread
{
#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)
enum class cv_status { no_timeout, timeout };
#else
using std::cv_status;
#endif
namespace xp
{
// Include the XP-compatible condition_variable classes only if actually
// compiling for XP. The XP-compatible classes are slower than the newer
// versions, and depend on features not compatible with Windows Phone 8.
#if (WINVER < _WIN32_WINNT_VISTA)
class condition_variable_any
{
recursive_mutex mMutex {};
std::atomic<int> mNumWaiters {0};
HANDLE mSemaphore;
HANDLE mWakeEvent {};
public:
using native_handle_type = HANDLE;
native_handle_type native_handle()
{
return mSemaphore;
}
condition_variable_any(const condition_variable_any&) = delete;
condition_variable_any& operator=(const condition_variable_any&) = delete;
condition_variable_any()
: mSemaphore(CreateSemaphoreA(NULL, 0, 0xFFFF, NULL))
{
if (mSemaphore == NULL)
throw std::system_error(GetLastError(), std::generic_category());
mWakeEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (mWakeEvent == NULL)
{
CloseHandle(mSemaphore);
throw std::system_error(GetLastError(), std::generic_category());
}
}
~condition_variable_any()
{
CloseHandle(mWakeEvent);
CloseHandle(mSemaphore);
}
private:
template <class M>
bool wait_impl(M& lock, DWORD timeout)
{
{
lock_guard<recursive_mutex> guard(mMutex);
mNumWaiters++;
}
lock.unlock();
DWORD ret = WaitForSingleObject(mSemaphore, timeout);
mNumWaiters--;
SetEvent(mWakeEvent);
lock.lock();
if (ret == WAIT_OBJECT_0)
return true;
else if (ret == WAIT_TIMEOUT)
return false;
//2 possible cases:
//1)The point in notify_all() where we determine the count to
//increment the semaphore with has not been reached yet:
//we just need to decrement mNumWaiters, but setting the event does not hurt
//
//2)Semaphore has just been released with mNumWaiters just before
//we decremented it. This means that the semaphore count
//after all waiters finish won't be 0 - because not all waiters
//woke up by acquiring the semaphore - we woke up by a timeout.
//The notify_all() must handle this gracefully
//
else
{
using namespace std;
throw system_error(make_error_code(errc::protocol_error));
}
}
public:
template <class M>
void wait(M& lock)
{
wait_impl(lock, INFINITE);
}
template <class M, class Predicate>
void wait(M& lock, Predicate pred)
{
while(!pred())
{
wait(lock);
};
}
void notify_all() noexcept
{
lock_guard<recursive_mutex> lock(mMutex); //block any further wait requests until all current waiters are unblocked
if (mNumWaiters.load() <= 0)
return;
ReleaseSemaphore(mSemaphore, mNumWaiters, NULL);
while(mNumWaiters > 0)
{
auto ret = WaitForSingleObject(mWakeEvent, 1000);
if (ret == WAIT_FAILED || ret == WAIT_ABANDONED)
std::terminate();
}
assert(mNumWaiters == 0);
//in case some of the waiters timed out just after we released the
//semaphore by mNumWaiters, it won't be zero now, because not all waiters
//woke up by acquiring the semaphore. So we must zero the semaphore before
//we accept waiters for the next event
//See _wait_impl for details
while(WaitForSingleObject(mSemaphore, 0) == WAIT_OBJECT_0);
}
void notify_one() noexcept
{
lock_guard<recursive_mutex> lock(mMutex);
int targetWaiters = mNumWaiters.load() - 1;
if (targetWaiters <= -1)
return;
ReleaseSemaphore(mSemaphore, 1, NULL);
while(mNumWaiters > targetWaiters)
{
auto ret = WaitForSingleObject(mWakeEvent, 1000);
if (ret == WAIT_FAILED || ret == WAIT_ABANDONED)
std::terminate();
}
assert(mNumWaiters == targetWaiters);
}
template <class M, class Rep, class Period>
cv_status wait_for(M& lock,
const std::chrono::duration<Rep, Period>& rel_time)
{
using namespace std::chrono;
auto timeout = duration_cast<milliseconds>(rel_time).count();
DWORD waittime = (timeout < INFINITE) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (INFINITE - 1);
bool ret = wait_impl(lock, waittime) || (timeout >= INFINITE);
return ret?cv_status::no_timeout:cv_status::timeout;
}
template <class M, class Rep, class Period, class Predicate>
bool wait_for(M& lock,
const std::chrono::duration<Rep, Period>& rel_time, Predicate pred)
{
return wait_until(lock, std::chrono::steady_clock::now()+rel_time, pred);
}
template <class M, class Clock, class Duration>
cv_status wait_until (M& lock,
const std::chrono::time_point<Clock,Duration>& abs_time)
{
return wait_for(lock, abs_time - Clock::now());
}
template <class M, class Clock, class Duration, class Predicate>
bool wait_until (M& lock,
const std::chrono::time_point<Clock, Duration>& abs_time,
Predicate pred)
{
while (!pred())
{
if (wait_until(lock, abs_time) == cv_status::timeout)
{
return pred();
}
}
return true;
}
};
class condition_variable: condition_variable_any
{
using base = condition_variable_any;
public:
using base::native_handle_type;
using base::native_handle;
using base::base;
using base::notify_all;
using base::notify_one;
void wait(unique_lock<mutex> &lock)
{
base::wait(lock);
}
template <class Predicate>
void wait(unique_lock<mutex>& lock, Predicate pred)
{
base::wait(lock, pred);
}
template <class Rep, class Period>
cv_status wait_for(unique_lock<mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time)
{
return base::wait_for(lock, rel_time);
}
template <class Rep, class Period, class Predicate>
bool wait_for(unique_lock<mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time, Predicate pred)
{
return base::wait_for(lock, rel_time, pred);
}
template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lock, const std::chrono::time_point<Clock,Duration>& abs_time)
{
return base::wait_until(lock, abs_time);
}
template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lock, const std::chrono::time_point<Clock, Duration>& abs_time, Predicate pred)
{
return base::wait_until(lock, abs_time, pred);
}
};
#endif // Compiling for XP
} // Namespace mingw_stdthread::xp
#if (WINVER >= _WIN32_WINNT_VISTA)
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;
#pragma GCC diagnostic pop
friend class condition_variable_any;
#if STDMUTEX_RECURSION_CHECKS
template<typename MTX>
inline static void before_wait (MTX * pmutex)
{
pmutex->mOwnerThread.checkSetOwnerBeforeUnlock();
}
template<typename MTX>
inline static void after_wait (MTX * pmutex)
{
pmutex->mOwnerThread.setOwnerAfterLock(GetCurrentThreadId());
}
#else
inline static void before_wait (void *) { }
inline static void after_wait (void *) { }
#endif
bool wait_impl (unique_lock<xp::mutex> & lock, DWORD time)
{
using mutex_handle_type = typename xp::mutex::native_handle_type;
static_assert(std::is_same<mutex_handle_type, PCRITICAL_SECTION>::value,
"Native Win32 condition variable requires std::mutex to \
use native Win32 critical section objects.");
xp::mutex * pmutex = lock.release();
before_wait(pmutex);
BOOL success = SleepConditionVariableCS(&cvariable_,
pmutex->native_handle(),
time);
after_wait(pmutex);
lock = unique_lock<xp::mutex>(*pmutex, adopt_lock);
return success;
}
bool wait_unique (windows7::mutex * pmutex, DWORD time)
{
before_wait(pmutex);
BOOL success = SleepConditionVariableSRW( native_handle(),
pmutex->native_handle(),
time,
// CONDITION_VARIABLE_LOCKMODE_SHARED has a value not specified by
// Microsoft's Dev Center, but is known to be (convertible to) a ULONG. To
// ensure that the value passed to this function is not equal to Microsoft's
// constant, we can either use a static_assert, or simply generate an
// appropriate value.
!CONDITION_VARIABLE_LOCKMODE_SHARED);
after_wait(pmutex);
return success;
}
bool wait_impl (unique_lock<windows7::mutex> & lock, DWORD time)
{
windows7::mutex * pmutex = lock.release();
bool success = wait_unique(pmutex, time);
lock = unique_lock<windows7::mutex>(*pmutex, adopt_lock);
return success;
}
public:
using native_handle_type = PCONDITION_VARIABLE;
native_handle_type native_handle (void)
{
return &cvariable_;
}
condition_variable (void) = default;
~condition_variable (void) = default;
condition_variable (const condition_variable &) = delete;
condition_variable & operator= (const condition_variable &) = delete;
void notify_one (void) noexcept
{
WakeConditionVariable(&cvariable_);
}
void notify_all (void) noexcept
{
WakeAllConditionVariable(&cvariable_);
}
void wait (unique_lock<mutex> & lock)
{
wait_impl(lock, kInfinite);
}
template<class Predicate>
void wait (unique_lock<mutex> & lock, Predicate pred)
{
while (!pred())
wait(lock);
}
template <class Rep, class Period>
cv_status wait_for(unique_lock<mutex>& lock,
const std::chrono::duration<Rep, Period>& rel_time)
{
using namespace std::chrono;
auto timeout = duration_cast<milliseconds>(rel_time).count();
DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (kInfinite - 1);
bool result = wait_impl(lock, waittime) || (timeout >= kInfinite);
return result ? cv_status::no_timeout : cv_status::timeout;
}
template <class Rep, class Period, class Predicate>
bool wait_for(unique_lock<mutex>& lock,
const std::chrono::duration<Rep, Period>& rel_time,
Predicate pred)
{
return wait_until(lock,
std::chrono::steady_clock::now() + rel_time,
std::move(pred));
}
template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lock,
const std::chrono::time_point<Clock,Duration>& abs_time)
{
return wait_for(lock, abs_time - Clock::now());
}
template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lock,
const std::chrono::time_point<Clock, Duration>& abs_time,
Predicate pred)
{
while (!pred())
{
if (wait_until(lock, abs_time) == cv_status::timeout)
{
return pred();
}
}
return true;
}
};
class condition_variable_any
{
static constexpr DWORD kInfinite = 0xffffffffl;
using native_shared_mutex = windows7::shared_mutex;
condition_variable internal_cv_ {};
// When available, the SRW-based mutexes should be faster than the
// CriticalSection-based mutexes. Only try_lock will be unavailable in Vista,
// and try_lock is not used by condition_variable_any.
windows7::mutex internal_mutex_ {};
template<class L>
bool wait_impl (L & lock, DWORD time)
{
unique_lock<decltype(internal_mutex_)> internal_lock(internal_mutex_);
lock.unlock();
bool success = internal_cv_.wait_impl(internal_lock, time);
lock.lock();
return success;
}
// If the lock happens to be called on a native Windows mutex, skip any extra
// contention.
inline bool wait_impl (unique_lock<mutex> & lock, DWORD time)
{
return internal_cv_.wait_impl(lock, time);
}
// Some shared_mutex functionality is available even in Vista, but it's not
// until Windows 7 that a full implementation is natively possible. The class
// itself is defined, with missing features, at the Vista feature level.
bool wait_impl (unique_lock<native_shared_mutex> & lock, DWORD time)
{
native_shared_mutex * pmutex = lock.release();
bool success = internal_cv_.wait_unique(pmutex, time);
lock = unique_lock<native_shared_mutex>(*pmutex, adopt_lock);
return success;
}
bool wait_impl (shared_lock<native_shared_mutex> & lock, DWORD time)
{
native_shared_mutex * pmutex = lock.release();
BOOL success = SleepConditionVariableSRW(native_handle(),
pmutex->native_handle(), time,
CONDITION_VARIABLE_LOCKMODE_SHARED);
lock = shared_lock<native_shared_mutex>(*pmutex, adopt_lock);
return success;
}
public:
using native_handle_type = typename condition_variable::native_handle_type;
native_handle_type native_handle (void)
{
return internal_cv_.native_handle();
}
void notify_one (void) noexcept
{
internal_cv_.notify_one();
}
void notify_all (void) noexcept
{
internal_cv_.notify_all();
}
condition_variable_any (void) = default;
~condition_variable_any (void) = default;
template<class L>
void wait (L & lock)
{
wait_impl(lock, kInfinite);
}
template<class L, class Predicate>
void wait (L & lock, Predicate pred)
{
while (!pred())
wait(lock);
}
template <class L, class Rep, class Period>
cv_status wait_for(L& lock, const std::chrono::duration<Rep,Period>& period)
{
using namespace std::chrono;
auto timeout = duration_cast<milliseconds>(period).count();
DWORD waittime = (timeout < kInfinite) ? ((timeout < 0) ? 0 : static_cast<DWORD>(timeout)) : (kInfinite - 1);
bool result = wait_impl(lock, waittime) || (timeout >= kInfinite);
return result ? cv_status::no_timeout : cv_status::timeout;
}
template <class L, class Rep, class Period, class Predicate>
bool wait_for(L& lock, const std::chrono::duration<Rep, Period>& period,
Predicate pred)
{
return wait_until(lock, std::chrono::steady_clock::now() + period,
std::move(pred));
}
template <class L, class Clock, class Duration>
cv_status wait_until (L& lock,
const std::chrono::time_point<Clock,Duration>& abs_time)
{
return wait_for(lock, abs_time - Clock::now());
}
template <class L, class Clock, class Duration, class Predicate>
bool wait_until (L& lock,
const std::chrono::time_point<Clock, Duration>& abs_time,
Predicate pred)
{
while (!pred())
{
if (wait_until(lock, abs_time) == cv_status::timeout)
{
return pred();
}
}
return true;
}
};
} // Namespace vista
#endif
#if WINVER < 0x0600
using xp::condition_variable;
using xp::condition_variable_any;
#else
using vista::condition_variable;
using vista::condition_variable_any;
#endif
} // Namespace mingw_stdthread
// Push objects into std, but only if they are not already there.
namespace std
{
// Because of quirks of the compiler, the common "using namespace std;"
// directive would flatten the namespaces and introduce ambiguity where there
// was none. Direct specification (std::), however, would be unaffected.
// Take the safe option, and include only in the presence of MinGW's win32
// implementation.
#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)
using mingw_stdthread::cv_status;
using mingw_stdthread::condition_variable;
using mingw_stdthread::condition_variable_any;
#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#pragma message "This version of MinGW seems to include a win32 port of\
pthreads, and probably already has C++11 std threading classes implemented,\
based on pthreads. These classes, found in namespace std, are not overridden\
by the mingw-std-thread library. If you would still like to use this\
implementation (as it is more lightweight), use the classes provided in\
namespace mingw_stdthread."
#endif
}
#endif // MINGW_CONDITIONAL_VARIABLE_H

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,491 @@
/**
* @file mingw.mutex.h
* @brief std::mutex et al implementation for MinGW
** (c) 2013-2016 by Mega Limited, Auckland, New Zealand
* @author Alexander Vassilev
*
* @copyright 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.
* @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 WIN32STDMUTEX_H
#define WIN32STDMUTEX_H
#if !defined(__cplusplus) || (__cplusplus < 201103L)
#error A C++11 compiler is required!
#endif
// Recursion checks on non-recursive locks have some performance penalty, and
// the C++ standard does not mandate them. The user might want to explicitly
// enable or disable such checks. If the user has no preference, enable such
// checks in debug builds, but not in release builds.
#ifdef STDMUTEX_RECURSION_CHECKS
#elif defined(NDEBUG)
#define STDMUTEX_RECURSION_CHECKS 0
#else
#define STDMUTEX_RECURSION_CHECKS 1
#endif
#include <chrono>
#include <system_error>
#include <atomic>
#include <mutex> //need for call_once()
#if STDMUTEX_RECURSION_CHECKS || !defined(NDEBUG)
#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"
#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.
#endif
namespace mingw_stdthread
{
// The _NonRecursive class has mechanisms that do not play nice with direct
// manipulation of the native handle. This forward declaration is part of
// a friend class declaration.
#if STDMUTEX_RECURSION_CHECKS
namespace vista
{
class condition_variable;
}
#endif
// To make this namespace equivalent to the thread-related subset of std,
// pull in the classes and class templates supplied by std but not by this
// implementation.
using std::lock_guard;
using std::unique_lock;
using std::adopt_lock_t;
using std::defer_lock_t;
using std::try_to_lock_t;
using std::adopt_lock;
using std::defer_lock;
using std::try_to_lock;
class recursive_mutex
{
CRITICAL_SECTION mHandle;
public:
typedef LPCRITICAL_SECTION native_handle_type;
native_handle_type native_handle() {return &mHandle;}
recursive_mutex() noexcept : mHandle()
{
InitializeCriticalSection(&mHandle);
}
recursive_mutex (const recursive_mutex&) = delete;
recursive_mutex& operator=(const recursive_mutex&) = delete;
~recursive_mutex() noexcept
{
DeleteCriticalSection(&mHandle);
}
void lock()
{
EnterCriticalSection(&mHandle);
}
void unlock()
{
LeaveCriticalSection(&mHandle);
}
bool try_lock()
{
return (TryEnterCriticalSection(&mHandle)!=0);
}
};
#if STDMUTEX_RECURSION_CHECKS
struct _OwnerThread
{
// If this is to be read before locking, then the owner-thread variable must
// be atomic to prevent a torn read from spuriously causing errors.
std::atomic<DWORD> mOwnerThread;
constexpr _OwnerThread () noexcept : mOwnerThread(0) {}
static void on_deadlock (void)
{
using namespace std;
fprintf(stderr, "FATAL: Recursive locking of non-recursive mutex\
detected. Throwing system exception\n");
fflush(stderr);
throw system_error(make_error_code(errc::resource_deadlock_would_occur));
}
DWORD checkOwnerBeforeLock() const
{
DWORD self = GetCurrentThreadId();
if (mOwnerThread.load(std::memory_order_relaxed) == self)
on_deadlock();
return self;
}
void setOwnerAfterLock(DWORD id)
{
mOwnerThread.store(id, std::memory_order_relaxed);
}
void checkSetOwnerBeforeUnlock()
{
DWORD self = GetCurrentThreadId();
if (mOwnerThread.load(std::memory_order_relaxed) != self)
on_deadlock();
mOwnerThread.store(0, std::memory_order_relaxed);
}
};
#endif
// 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.
#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA)
namespace windows7
{
class mutex
{
SRWLOCK mHandle;
// Track locking thread for error checking.
#if STDMUTEX_RECURSION_CHECKS
friend class vista::condition_variable;
_OwnerThread mOwnerThread {};
#endif
public:
typedef PSRWLOCK native_handle_type;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
constexpr mutex () noexcept : mHandle(SRWLOCK_INIT) { }
#pragma GCC diagnostic pop
mutex (const mutex&) = delete;
mutex & operator= (const mutex&) = delete;
void lock (void)
{
// Note: Undefined behavior if called recursively.
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
AcquireSRWLockExclusive(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.setOwnerAfterLock(self);
#endif
}
void unlock (void)
{
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.checkSetOwnerBeforeUnlock();
#endif
ReleaseSRWLockExclusive(&mHandle);
}
// TryAcquireSRW functions are a Windows 7 feature.
#if (WINVER >= _WIN32_WINNT_WIN7)
bool try_lock (void)
{
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
BOOL ret = TryAcquireSRWLockExclusive(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
if (ret)
mOwnerThread.setOwnerAfterLock(self);
#endif
return ret;
}
#endif
native_handle_type native_handle (void)
{
return &mHandle;
}
};
} // Namespace windows7
#endif // Compiling for Vista
namespace xp
{
class mutex
{
CRITICAL_SECTION mHandle;
std::atomic_uchar mState;
// Track locking thread for error checking.
#if STDMUTEX_RECURSION_CHECKS
friend class vista::condition_variable;
_OwnerThread mOwnerThread {};
#endif
public:
typedef PCRITICAL_SECTION native_handle_type;
constexpr mutex () noexcept : mHandle(), mState(2) { }
mutex (const mutex&) = delete;
mutex & operator= (const mutex&) = delete;
~mutex() noexcept
{
// Undefined behavior if the mutex is held (locked) by any thread.
// Undefined behavior if a thread terminates while holding ownership of the
// mutex.
DeleteCriticalSection(&mHandle);
}
void lock (void)
{
unsigned char state = mState.load(std::memory_order_acquire);
while (state) {
if ((state == 2) && mState.compare_exchange_weak(state, 1, std::memory_order_acquire))
{
InitializeCriticalSection(&mHandle);
mState.store(0, std::memory_order_release);
break;
}
if (state == 1)
{
Sleep(0);
state = mState.load(std::memory_order_acquire);
}
}
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
EnterCriticalSection(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.setOwnerAfterLock(self);
#endif
}
void unlock (void)
{
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.checkSetOwnerBeforeUnlock();
#endif
LeaveCriticalSection(&mHandle);
}
bool try_lock (void)
{
unsigned char state = mState.load(std::memory_order_acquire);
if ((state == 2) && mState.compare_exchange_strong(state, 1, std::memory_order_acquire))
{
InitializeCriticalSection(&mHandle);
mState.store(0, std::memory_order_release);
}
if (state == 1)
return false;
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
BOOL ret = TryEnterCriticalSection(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
if (ret)
mOwnerThread.setOwnerAfterLock(self);
#endif
return ret;
}
native_handle_type native_handle (void)
{
return &mHandle;
}
};
} // Namespace "xp"
#if (WINVER >= _WIN32_WINNT_WIN7)
using windows7::mutex;
#else
using xp::mutex;
#endif
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 == kWaitAbandoned)
{
using namespace std;
fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate();
}
#endif
return (ret == kWaitObject0) || (ret == kWaitAbandoned);
}
protected:
HANDLE mHandle;
// Track locking thread for error checking of non-recursive timed_mutex. For
// standard compliance, this must be defined in same class and at the same
// access-control level as every other variable in the timed_mutex.
#if STDMUTEX_RECURSION_CHECKS
friend class vista::condition_variable;
_OwnerThread mOwnerThread {};
#endif
public:
typedef HANDLE native_handle_type;
native_handle_type native_handle() const {return mHandle;}
recursive_timed_mutex(const recursive_timed_mutex&) = delete;
recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete;
recursive_timed_mutex(): mHandle(CreateMutex(NULL, FALSE, NULL)) {}
~recursive_timed_mutex()
{
CloseHandle(mHandle);
}
void lock()
{
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 == kWaitAbandoned)
{
using namespace std;
fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate();
}
#endif
if ((ret != kWaitObject0) && (ret != kWaitAbandoned))
{
throw std::system_error(GetLastError(), std::system_category());
}
}
void unlock()
{
if (!ReleaseMutex(mHandle))
throw std::system_error(GetLastError(), std::system_category());
}
bool try_lock()
{
return try_lock_internal(0);
}
template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep,Period>& dur)
{
using namespace std::chrono;
auto timeout = duration_cast<milliseconds>(dur).count();
while (timeout > 0)
{
constexpr auto kMaxStep = static_cast<decltype(timeout)>(kInfinite-1);
auto step = (timeout < kMaxStep) ? timeout : kMaxStep;
if (try_lock_internal(static_cast<DWORD>(step)))
return true;
timeout -= step;
}
return false;
}
template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock,Duration>& timeout_time)
{
return try_lock_for(timeout_time - Clock::now());
}
};
// Override if, and only if, it is necessary for error-checking.
#if STDMUTEX_RECURSION_CHECKS
class timed_mutex: recursive_timed_mutex
{
public:
timed_mutex(const timed_mutex&) = delete;
timed_mutex& operator=(const timed_mutex&) = delete;
void lock()
{
DWORD self = mOwnerThread.checkOwnerBeforeLock();
recursive_timed_mutex::lock();
mOwnerThread.setOwnerAfterLock(self);
}
void unlock()
{
mOwnerThread.checkSetOwnerBeforeUnlock();
recursive_timed_mutex::unlock();
}
template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep,Period>& dur)
{
DWORD self = mOwnerThread.checkOwnerBeforeLock();
bool ret = recursive_timed_mutex::try_lock_for(dur);
if (ret)
mOwnerThread.setOwnerAfterLock(self);
return ret;
}
template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock,Duration>& timeout_time)
{
return try_lock_for(timeout_time - Clock::now());
}
bool try_lock ()
{
return try_lock_for(std::chrono::milliseconds(0));
}
};
#else
typedef recursive_timed_mutex timed_mutex;
#endif
class once_flag
{
// When available, the SRW-based mutexes should be faster than the
// CriticalSection-based mutexes. Only try_lock will be unavailable in Vista,
// and try_lock is not used by once_flag.
#if (_WIN32_WINNT == _WIN32_WINNT_VISTA)
windows7::mutex mMutex;
#else
mutex mMutex;
#endif
std::atomic_bool mHasRun;
once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;
template<class Callable, class... Args>
friend void call_once(once_flag& once, Callable&& f, Args&&... args);
public:
constexpr once_flag() noexcept: mMutex(), mHasRun(false) {}
};
template<class Callable, class... Args>
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))
return;
detail::invoke(std::forward<Callable>(func),std::forward<Args>(args)...);
flag.mHasRun.store(true, std::memory_order_release);
}
} // Namespace mingw_stdthread
// Push objects into std, but only if they are not already there.
namespace std
{
// Because of quirks of the compiler, the common "using namespace std;"
// directive would flatten the namespaces and introduce ambiguity where there
// was none. Direct specification (std::), however, would be unaffected.
// Take the safe option, and include only in the presence of MinGW's win32
// implementation.
#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)
using mingw_stdthread::recursive_mutex;
using mingw_stdthread::mutex;
using mingw_stdthread::recursive_timed_mutex;
using mingw_stdthread::timed_mutex;
using mingw_stdthread::once_flag;
using mingw_stdthread::call_once;
#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#pragma message "This version of MinGW seems to include a win32 port of\
pthreads, and probably already has C++11 std threading classes implemented,\
based on pthreads. These classes, found in namespace std, are not overridden\
by the mingw-std-thread library. If you would still like to use this\
implementation (as it is more lightweight), use the classes provided in\
namespace mingw_stdthread."
#endif
}
#endif // WIN32STDMUTEX_H

View File

@@ -0,0 +1,503 @@
/// \file mingw.shared_mutex.h
/// \brief Standard-compliant shared_mutex for MinGW
///
/// (c) 2017 by Nathaniel J. McClatchey, Athens OH, United States
/// \author Nathaniel J. McClatchey
///
/// \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.
/// \note Target Windows version is determined by WINVER, which is determined in
/// <windows.h> from _WIN32_WINNT, which can itself be set by the user.
// Notes on the namespaces:
// - The implementation can be accessed directly in the namespace
// mingw_stdthread.
// - Objects will be brought into namespace std by a using directive. This
// will cause objects declared in std (such as MinGW's implementation) to
// hide this implementation's definitions.
// - To avoid poluting the namespace with implementation details, all objects
// to be pushed into std will be placed in mingw_stdthread::visible.
// The end result is that if MinGW supplies an object, it is automatically
// used. If MinGW does not supply an object, this implementation's version will
// instead be used.
#ifndef MINGW_SHARED_MUTEX_H_
#define MINGW_SHARED_MUTEX_H_
#if !defined(__cplusplus) || (__cplusplus < 201103L)
#error A C++11 compiler is required!
#endif
#include <cassert>
// For descriptive errors.
#include <system_error>
// Implementing a shared_mutex without OS support will require atomic read-
// modify-write capacity.
#include <atomic>
// For timing in shared_lock and shared_timed_mutex.
#include <chrono>
#include <limits>
// Use MinGW's shared_lock class template, if it's available. Requires C++14.
// If unavailable (eg. because this library is being used in C++11), then an
// implementation of shared_lock is provided by this header.
#if (__cplusplus >= 201402L)
#include <shared_mutex>
#endif
// For defer_lock_t, adopt_lock_t, and try_to_lock_t
#include "mingw.mutex.h"
// For this_thread::yield.
//#include "mingw.thread.h"
// 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
{
// Define a portable atomics-based shared_mutex
namespace portable
{
class shared_mutex
{
typedef uint_fast16_t counter_type;
std::atomic<counter_type> mCounter {0};
static constexpr counter_type kWriteBit = 1 << (std::numeric_limits<counter_type>::digits - 1);
#if STDMUTEX_RECURSION_CHECKS
// Runtime checker for verifying owner threads. Note: Exclusive mode only.
_OwnerThread mOwnerThread {};
#endif
public:
typedef shared_mutex * native_handle_type;
shared_mutex () = default;
// No form of copying or moving should be allowed.
shared_mutex (const shared_mutex&) = delete;
shared_mutex & operator= (const shared_mutex&) = delete;
~shared_mutex ()
{
// Terminate if someone tries to destroy an owned mutex.
assert(mCounter.load(std::memory_order_relaxed) == 0);
}
void lock_shared (void)
{
counter_type expected = mCounter.load(std::memory_order_relaxed);
do
{
// Delay if writing or if too many readers are attempting to read.
if (expected >= kWriteBit - 1)
{
using namespace std;
expected = mCounter.load(std::memory_order_relaxed);
continue;
}
if (mCounter.compare_exchange_weak(expected,
static_cast<counter_type>(expected + 1),
std::memory_order_acquire,
std::memory_order_relaxed))
break;
}
while (true);
}
bool try_lock_shared (void)
{
counter_type expected = mCounter.load(std::memory_order_relaxed) & static_cast<counter_type>(~kWriteBit);
if (expected + 1 == kWriteBit)
return false;
else
return mCounter.compare_exchange_strong( expected,
static_cast<counter_type>(expected + 1),
std::memory_order_acquire,
std::memory_order_relaxed);
}
void unlock_shared (void)
{
using namespace std;
#ifndef NDEBUG
if (!(mCounter.fetch_sub(1, memory_order_release) & static_cast<counter_type>(~kWriteBit)))
throw system_error(make_error_code(errc::operation_not_permitted));
#else
mCounter.fetch_sub(1, memory_order_release);
#endif
}
// Behavior is undefined if a lock was previously acquired.
void lock (void)
{
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
using namespace std;
// 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();
// Wait for readers to finish up.
while (current != kWriteBit)
{
//this_thread::yield();
current = mCounter.load(std::memory_order_acquire);
}
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.setOwnerAfterLock(self);
#endif
}
bool try_lock (void)
{
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
counter_type expected = 0;
bool ret = mCounter.compare_exchange_strong(expected, kWriteBit,
std::memory_order_acquire,
std::memory_order_relaxed);
#if STDMUTEX_RECURSION_CHECKS
if (ret)
mOwnerThread.setOwnerAfterLock(self);
#endif
return ret;
}
void unlock (void)
{
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.checkSetOwnerBeforeUnlock();
#endif
using namespace std;
#ifndef NDEBUG
if (mCounter.load(memory_order_relaxed) != kWriteBit)
throw system_error(make_error_code(errc::operation_not_permitted));
#endif
mCounter.store(0, memory_order_release);
}
native_handle_type native_handle (void)
{
return this;
}
};
} // Namespace portable
// The native shared_mutex implementation primarily uses features of Windows
// Vista, but the features used for try_lock and try_lock_shared were not
// introduced until Windows 7. To allow limited use while compiling for Vista,
// I define the class without try_* functions in that case.
// Only fully-featured implementations will be placed into namespace std.
#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA)
namespace vista
{
class condition_variable_any;
}
namespace windows7
{
// We already #include "mingw.mutex.h". May as well reduce redundancy.
class shared_mutex : windows7::mutex
{
// Allow condition_variable_any (and only condition_variable_any) to treat a
// shared_mutex as its base class.
friend class vista::condition_variable_any;
public:
using windows7::mutex::native_handle_type;
using windows7::mutex::lock;
using windows7::mutex::unlock;
using windows7::mutex::native_handle;
void lock_shared (void)
{
AcquireSRWLockShared(native_handle());
}
void unlock_shared (void)
{
ReleaseSRWLockShared(native_handle());
}
// TryAcquireSRW functions are a Windows 7 feature.
#if (WINVER >= _WIN32_WINNT_WIN7)
bool try_lock_shared (void)
{
return TryAcquireSRWLockShared(native_handle()) != 0;
}
using windows7::mutex::try_lock;
#endif
};
} // Namespace windows7
#endif // Compiling for Vista
#if (defined(_WIN32) && (WINVER >= _WIN32_WINNT_WIN7))
using windows7::shared_mutex;
#else
using portable::shared_mutex;
#endif
class shared_timed_mutex : shared_mutex
{
typedef shared_mutex Base;
public:
using Base::lock;
using Base::try_lock;
using Base::unlock;
using Base::lock_shared;
using Base::try_lock_shared;
using Base::unlock_shared;
template< class Clock, class Duration >
bool try_lock_until ( const std::chrono::time_point<Clock,Duration>& cutoff )
{
do
{
if (try_lock())
return true;
}
while (std::chrono::steady_clock::now() < cutoff);
return false;
}
template< class Rep, class Period >
bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time)
{
return try_lock_until(std::chrono::steady_clock::now() + rel_time);
}
template< class Clock, class Duration >
bool try_lock_shared_until ( const std::chrono::time_point<Clock,Duration>& cutoff )
{
do
{
if (try_lock_shared())
return true;
}
while (std::chrono::steady_clock::now() < cutoff);
return false;
}
template< class Rep, class Period >
bool try_lock_shared_for (const std::chrono::duration<Rep,Period>& rel_time)
{
return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time);
}
};
#if __cplusplus >= 201402L
using std::shared_lock;
#else
// If not supplied by shared_mutex (eg. because C++14 is not supported), I
// supply the various helper classes that the header should have defined.
template<class Mutex>
class shared_lock
{
Mutex * mMutex;
bool mOwns;
// Reduce code redundancy
void verify_lockable (void)
{
using namespace std;
if (mMutex == nullptr)
throw system_error(make_error_code(errc::operation_not_permitted));
if (mOwns)
throw system_error(make_error_code(errc::resource_deadlock_would_occur));
}
public:
typedef Mutex mutex_type;
shared_lock (void) noexcept
: mMutex(nullptr), mOwns(false)
{
}
shared_lock (shared_lock<Mutex> && other) noexcept
: mMutex(other.mutex_), mOwns(other.owns_)
{
other.mMutex = nullptr;
other.mOwns = false;
}
explicit shared_lock (mutex_type & m)
: mMutex(&m), mOwns(true)
{
mMutex->lock_shared();
}
shared_lock (mutex_type & m, defer_lock_t) noexcept
: mMutex(&m), mOwns(false)
{
}
shared_lock (mutex_type & m, adopt_lock_t)
: mMutex(&m), mOwns(true)
{
}
shared_lock (mutex_type & m, try_to_lock_t)
: mMutex(&m), mOwns(m.try_lock_shared())
{
}
template< class Rep, class Period >
shared_lock( mutex_type& m, const std::chrono::duration<Rep,Period>& timeout_duration )
: mMutex(&m), mOwns(m.try_lock_shared_for(timeout_duration))
{
}
template< class Clock, class Duration >
shared_lock( mutex_type& m, const std::chrono::time_point<Clock,Duration>& timeout_time )
: mMutex(&m), mOwns(m.try_lock_shared_until(timeout_time))
{
}
shared_lock& operator= (shared_lock<Mutex> && other) noexcept
{
if (&other != this)
{
if (mOwns)
mMutex->unlock_shared();
mMutex = other.mMutex;
mOwns = other.mOwns;
other.mMutex = nullptr;
other.mOwns = false;
}
return *this;
}
~shared_lock (void)
{
if (mOwns)
mMutex->unlock_shared();
}
shared_lock (const shared_lock<Mutex> &) = delete;
shared_lock& operator= (const shared_lock<Mutex> &) = delete;
// Shared locking
void lock (void)
{
verify_lockable();
mMutex->lock_shared();
mOwns = true;
}
bool try_lock (void)
{
verify_lockable();
mOwns = mMutex->try_lock_shared();
return mOwns;
}
template< class Clock, class Duration >
bool try_lock_until( const std::chrono::time_point<Clock,Duration>& cutoff )
{
verify_lockable();
do
{
mOwns = mMutex->try_lock_shared();
if (mOwns)
return mOwns;
}
while (std::chrono::steady_clock::now() < cutoff);
return false;
}
template< class Rep, class Period >
bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time)
{
return try_lock_until(std::chrono::steady_clock::now() + rel_time);
}
void unlock (void)
{
using namespace std;
if (!mOwns)
throw system_error(make_error_code(errc::operation_not_permitted));
mMutex->unlock_shared();
mOwns = false;
}
// Modifiers
void swap (shared_lock<Mutex> & other) noexcept
{
using namespace std;
swap(mMutex, other.mMutex);
swap(mOwns, other.mOwns);
}
mutex_type * release (void) noexcept
{
mutex_type * ptr = mMutex;
mMutex = nullptr;
mOwns = false;
return ptr;
}
// Observers
mutex_type * mutex (void) const noexcept
{
return mMutex;
}
bool owns_lock (void) const noexcept
{
return mOwns;
}
explicit operator bool () const noexcept
{
return owns_lock();
}
};
template< class Mutex >
void swap( shared_lock<Mutex>& lhs, shared_lock<Mutex>& rhs ) noexcept
{
lhs.swap(rhs);
}
#endif // C++11
} // Namespace mingw_stdthread
namespace std
{
// Because of quirks of the compiler, the common "using namespace std;"
// directive would flatten the namespaces and introduce ambiguity where there
// was none. Direct specification (std::), however, would be unaffected.
// Take the safe option, and include only in the presence of MinGW's win32
// implementation.
#if (__cplusplus < 201703L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS))
using mingw_stdthread::shared_mutex;
#endif
#if (__cplusplus < 201402L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS))
using mingw_stdthread::shared_timed_mutex;
using mingw_stdthread::shared_lock;
#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#pragma message "This version of MinGW seems to include a win32 port of\
pthreads, and probably already has C++ std threading classes implemented,\
based on pthreads. These classes, found in namespace std, are not overridden\
by the mingw-std-thread library. If you would still like to use this\
implementation (as it is more lightweight), use the classes provided in\
namespace mingw_stdthread."
#endif
} // Namespace std
#endif // MINGW_SHARED_MUTEX_H_

View File

@@ -0,0 +1,360 @@
/**
* @file mingw.thread.h
* @brief std::thread implementation for MinGW
* (c) 2013-2016 by Mega Limited, Auckland, New Zealand
* @author Alexander Vassilev
*
* @copyright 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.
* @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 WIN32STDTHREAD_H
#define WIN32STDTHREAD_H
#if !defined(__cplusplus) || (__cplusplus < 201103L)
#error A C++11 compiler is required!
#endif
// Use the standard classes for std::, if available.
#include <thread>
#include <cstddef> // For std::size_t
#include <cerrno> // Detect error type.
#include <exception> // For std::terminate
#include <system_error> // For std::system_error
#include <functional> // For std::hash
#include <tuple> // For std::tuple
#include <chrono> // For sleep timing.
#include <memory> // For std::unique_ptr
#include <iosfwd> // Stream output for thread ids.
#include <utility> // For std::swap, std::forward
#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
#include <cstdio>
#endif
#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.
#endif
// Instead of INVALID_HANDLE_VALUE, _beginthreadex returns 0.
namespace mingw_stdthread
{
namespace detail
{
template<std::size_t...>
struct IntSeq {};
template<std::size_t N, std::size_t... S>
struct GenIntSeq : GenIntSeq<N-1, N-1, S...> { };
template<std::size_t... S>
struct GenIntSeq<0, S...> { typedef IntSeq<S...> type; };
// 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;
public:
ThreadFuncCall(Func&& aFunc, Args&&... aArgs)
: mFunc(std::forward<Func>(aFunc)),
mArgs(std::forward<Args>(aArgs)...)
{
}
void callFunc()
{
detail::invoke(std::move(mFunc), std::move(std::get<S>(mArgs)) ...);
}
};
// Allow construction of threads without exposing implementation.
class ThreadIdTool;
} // Namespace "detail"
class thread
{
public:
class id
{
DWORD mId = 0;
friend class thread;
friend class std::hash<id>;
friend class detail::ThreadIdTool;
explicit id(DWORD aId) noexcept : mId(aId){}
public:
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; }
template<class _CharT, class _Traits>
friend std::basic_ostream<_CharT, _Traits>&
operator<<(std::basic_ostream<_CharT, _Traits>& __out, id __id)
{
if (__id.mId == 0)
{
return __out << "(invalid std::thread::id)";
}
else
{
return __out << __id.mId;
}
}
};
private:
static constexpr HANDLE kInvalidHandle = nullptr;
static constexpr DWORD kInfinite = 0xffffffffl;
HANDLE mHandle;
id mThreadId;
template <class Call>
static unsigned __stdcall threadfunc(void* arg)
{
std::unique_ptr<Call> call(static_cast<Call*>(arg));
call->callFunc();
return 0;
}
static unsigned int _hardware_concurrency_helper() noexcept
{
SYSTEM_INFO sysinfo;
// This is one of the few functions used by the library which has a nearly-
// equivalent function defined in earlier versions of Windows. Include the
// workaround, just as a reminder that it does exist.
#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501)
::GetNativeSystemInfo(&sysinfo);
#else
::GetSystemInfo(&sysinfo);
#endif
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)
:mHandle(other.mHandle), mThreadId(other.mThreadId)
{
other.mHandle = kInvalidHandle;
other.mThreadId = id{};
}
thread(const thread &other)=delete;
template<class Func, typename... Args>
explicit thread(Func&& func, Args&&... args) : mHandle(), mThreadId()
{
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, &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<HANDLE>(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())
{
#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())
{
#ifndef NDEBUG
std::printf("Error: Must join() or detach() a thread before \
moving another thread to it.\n");
#endif
std::terminate();
}
swap(std::forward<thread>(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
{
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 "detail"
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<Rep,Period>& sleep_duration)
{
static constexpr DWORD kInfinite = 0xffffffffl;
using namespace std::chrono;
using rep = milliseconds::rep;
rep ms = duration_cast<milliseconds>(sleep_duration).count();
while (ms > 0)
{
constexpr rep kMaxRep = static_cast<rep>(kInfinite - 1);
auto sleepTime = (ms < kMaxRep) ? ms : kMaxRep;
Sleep(static_cast<DWORD>(sleepTime));
ms -= sleepTime;
}
}
template <class Clock, class Duration>
void sleep_until(const std::chrono::time_point<Clock,Duration>& sleep_time)
{
sleep_for(sleep_time-Clock::now());
}
}
} // Namespace mingw_stdthread
namespace std
{
// Because of quirks of the compiler, the common "using namespace std;"
// directive would flatten the namespaces and introduce ambiguity where there
// was none. Direct specification (std::), however, would be unaffected.
// Take the safe option, and include only in the presence of MinGW's win32
// implementation.
#if defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)
using mingw_stdthread::thread;
// Remove ambiguity immediately, to avoid problems arising from the above.
//using std::thread;
namespace this_thread
{
using namespace mingw_stdthread::this_thread;
}
#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#pragma message "This version of MinGW seems to include a win32 port of\
pthreads, and probably already has C++11 std threading classes implemented,\
based on pthreads. These classes, found in namespace std, are not overridden\
by the mingw-std-thread library. If you would still like to use this\
implementation (as it is more lightweight), use the classes provided in\
namespace mingw_stdthread."
#endif
// Specialize hash for this implementation's thread::id, even if the
// std::thread::id already has a hash.
template<>
struct hash<mingw_stdthread::thread::id>
{
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 // WIN32STDTHREAD_H

View File

@@ -0,0 +1,18 @@
project(stdthreadtest)
cmake_minimum_required(VERSION 3.0)
string(CONCAT mingw_stdthreads_tests_compile_options_docstring
"Compiler flags used to compile mingw-stdthreads's tests. By default "
"it's -std=c++11 -Wall -Wextra")
set(MINGW_STDTHREADS_TESTS_COMPILE_OPTIONS "-std=c++11;-Wall;-Wextra"
CACHE STRING ${mingw_stdthreads_tests_compile_options_docstring})
set(MINGW_STDTHREADS_TESTS_ADDITIONAL_LINKER_FLAGS "" CACHE STRING
"Optional linker flags to be passed when linking mingw-stdthreads's tests")
add_executable(${PROJECT_NAME} tests.cpp)
target_compile_options(${PROJECT_NAME} PRIVATE
${MINGW_STDTHREADS_TESTS_COMPILE_OPTIONS})
target_link_libraries(${PROJECT_NAME} PRIVATE mingw_stdthreads)
target_link_libraries(${PROJECT_NAME} PRIVATE
${MINGW_STDTHREADS_TESTS_ADDITIONAL_LINKER_FLAGS})

View File

@@ -0,0 +1,450 @@
#ifndef MINGW_STDTHREADS_GENERATED_STDHEADERS
#include <mingw.thread.h>
#include <mingw.mutex.h>
#include <mingw.condition_variable.h>
#include <mingw.shared_mutex.h>
#include <mingw.future.h>
#else
#include <thread>
#include <mutex>
#include <condition_variable>
#include <shared_mutex>
#include <future>
#endif
#include <atomic>
#include <cassert>
#include <string>
#include <iostream>
#include <typeinfo>
using namespace std;
int test_int = 42;
// Pre-declaration to suppress some warnings.
void test_call_once(int, char const *);
int cond = 0;
std::mutex m;
std::shared_mutex sm;
std::condition_variable cv;
std::condition_variable_any cv_any;
template<class ... Args>
void log (char const * fmtString, Args ...args) {
printf(fmtString, args...);
printf("\n");
fflush(stdout);
}
void test_call_once(int a, const char* str)
{
log("test_call_once called with a=%d, str=%s", a, str);
this_thread::sleep_for(std::chrono::milliseconds(500));
}
struct TestMove
{
std::string mStr;
TestMove(const std::string& aStr): mStr(aStr){}
TestMove(TestMove&& other): mStr(other.mStr+" moved")
{ printf("%s: Object moved\n", mStr.c_str()); }
TestMove(const TestMove&) : mStr()
{
assert(false && "TestMove: Object COPIED instead of moved");
}
};
template<class T>
void test_future_set_value (promise<T> & promise)
{
promise.set_value(T(test_int));
}
template<>
void test_future_set_value (promise<void> & promise)
{
promise.set_value();
}
template<class T>
bool test_future_get_value (future<T> & future)
{
return (future.get() == T(test_int));
}
template<>
bool test_future_get_value (future<void> & future)
{
future.get();
return true;
}
template<class T>
struct CustomAllocator
{
CustomAllocator (void) noexcept
{
}
template<class U>
CustomAllocator (CustomAllocator<U> const &) noexcept
{
}
template<class U>
CustomAllocator<T> & operator= (CustomAllocator<U> const &) noexcept
{
return *this;
}
typedef T value_type;
T * allocate (size_t n)
{
log("Used custom allocator to allocate %zu object(s).", n);
return static_cast<T*>(std::malloc(n * sizeof(T)));
}
void deallocate (T * ptr, size_t n)
{
log("Used custom allocator to deallocate %zu object(s).", n);
std::free(ptr);
}
};
template<class T>
void test_future ()
{
static_assert(is_move_constructible<promise<T> >::value,
"std::promise must be move-constructible.");
static_assert(is_move_assignable<promise<T> >::value,
"std::promise must be move-assignable.");
static_assert(!is_copy_constructible<promise<T> >::value,
"std::promise must not be copy-constructible.");
static_assert(!is_copy_assignable<promise<T> >::value,
"std::promise must not be copy-assignable.");
static_assert(is_move_constructible<future<T> >::value,
"std::future must be move-constructible.");
static_assert(is_move_assignable<future<T> >::value,
"std::future must be move-assignable.");
static_assert(!is_copy_constructible<future<T> >::value,
"std::future must not be copy-constructible.");
static_assert(!is_copy_assignable<future<T> >::value,
"std::future must not be copy-assignable.");
static_assert(is_move_constructible<shared_future<T> >::value,
"std::shared_future must be move-constructible.");
static_assert(is_move_assignable<shared_future<T> >::value,
"std::shared_future must be move-assignable.");
static_assert(is_copy_constructible<shared_future<T> >::value,
"std::shared_future must be copy-constructible.");
static_assert(is_copy_assignable<shared_future<T> >::value,
"std::shared_future must be copy-assignable.");
log("\tMaking a few promises, and getting their futures...");
promise<T> promise_value, promise_exception, promise_broken, promise_late;
future<T> future_value = promise_value.get_future();
future<T> future_exception = promise_exception.get_future();
future<T> future_broken = promise_broken.get_future();
future<T> future_late = promise_late.get_future();
try {
future<T> impossible_future = promise_value.get_future();
log("WARNING: Promise failed to detect that its future was already retrieved.");
} catch(...) {
log("\tPromise successfully prevented redundant future retrieval.");
}
log("\tPassing promises to a new thread...");
thread t ([](promise<T> p_value, promise<T> p_exception, promise<T>, promise<T> p_late)
{
this_thread::sleep_for(std::chrono::seconds(1));
try {
throw std::runtime_error("Thrown during the thread.");
} catch (...) {
p_late.set_exception_at_thread_exit(std::current_exception());
}
test_future_set_value(p_value);
try {
throw std::runtime_error("Things happened as expected.");
} catch (...) {
p_exception.set_exception(std::current_exception());
}
this_thread::sleep_for(std::chrono::seconds(2));
},
std::move(promise_value),
std::move(promise_exception),
std::move(promise_broken),
std::move(promise_late));
t.detach();
try {
bool was_expected = test_future_get_value(future_value);
log("\tReceived %sexpected value.", (was_expected ? "" : "un"));
} catch (...) {
log("WARNING: Exception where there should be none!");
throw;
}
try {
test_future_get_value(future_exception);
log("WARNING: Got a value where there should be an exception!");
} catch (std::exception & e) {
log("\tReceived an exception (\"%s\") as expected.", e.what());
}
log("\tWaiting for the thread to exit...");
try {
test_future_get_value(future_late);
log("WARNING: Got a value where there should be an exception!");
} catch (std::exception & e) {
log("\tReceived an exception (\"%s\") as expected.", e.what());
}
try {
test_future_get_value(future_broken);
log("WARNING: Got a value where there should be an exception!");
} catch (std::future_error & e) {
log("\tReceived a future_error (\"%s\") as expected.", e.what());
}
log("\tDeferring a function...");
auto async_deferred = async(launch::deferred, [] (void) -> T
{
std::hash<std::thread::id> hasher;
log("\t\tDeferred function called on thread %zu", hasher(std::this_thread::get_id()));
if (!is_void<T>::value)
return T(test_int);
});
log("\tCalling a function asynchronously...");
auto async_async = async(launch::async, [] (void) -> T
{
std::hash<std::thread::id> hasher;
log("\t\tAsynchronous function called on thread %zu", hasher(std::this_thread::get_id()));
if (!is_void<T>::value)
return T(test_int);
});
log("\tLetting the implementation decide...");
auto async_either = async([] (thread::id other_id) -> T
{
std::hash<thread::id> hasher;
log("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous");
if (!is_void<T>::value)
return T(test_int);
}, this_thread::get_id());
log("\tFetching asynchronous result.");
test_future_get_value(async_async);
log("\tFetching deferred result.");
test_future_get_value(async_deferred);
log("\tFetching implementation-defined result.");
test_future_get_value(async_either);
log("\tTesting async on pointer-to-member-function.");
struct Helper
{
thread::id other_id;
T call (void) const
{
std::hash<thread::id> hasher;
log("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous");
if (!is_void<T>::value)
return T(test_int);
}
} test_class { this_thread::get_id() };
auto async_member = async(Helper::call, test_class);
log("\tFetching result.");
test_future_get_value(async_member);
}
#define TEST_SL_MV_CPY(ClassName) \
static_assert(std::is_standard_layout<ClassName>::value, \
"ClassName does not satisfy concept StandardLayoutType."); \
static_assert(!std::is_move_constructible<ClassName>::value, \
"ClassName must not be move-constructible."); \
static_assert(!std::is_move_assignable<ClassName>::value, \
"ClassName must not be move-assignable."); \
static_assert(!std::is_copy_constructible<ClassName>::value, \
"ClassName must not be copy-constructible."); \
static_assert(!std::is_copy_assignable<ClassName>::value, \
"ClassName must not be copy-assignable.");
int main()
{
#ifdef MINGW_STDTHREADS_GENERATED_STDHEADERS
std::cout << "Using cmake-generated stdheaders, ";
#endif
static_assert(std::is_trivially_copyable<thread::id>::value,
"thread::id must be trivially copyable.");
TEST_SL_MV_CPY(mutex)
TEST_SL_MV_CPY(recursive_mutex)
TEST_SL_MV_CPY(timed_mutex)
TEST_SL_MV_CPY(recursive_timed_mutex)
TEST_SL_MV_CPY(shared_mutex)
TEST_SL_MV_CPY(shared_timed_mutex)
TEST_SL_MV_CPY(condition_variable)
TEST_SL_MV_CPY(condition_variable_any)
static_assert(!std::is_move_constructible<once_flag>::value,
"once_flag must not be move-constructible.");
static_assert(!std::is_move_assignable<once_flag>::value,
"once_flag must not be move-assignable.");
static_assert(!std::is_copy_constructible<once_flag>::value,
"once_flag must not be copy-constructible.");
static_assert(!std::is_copy_assignable<once_flag>::value,
"once_flag must not be copy-assignable.");
// With C++ feature level and target Windows version potentially affecting
// behavior, make this information visible.
{
switch (__cplusplus)
{
case 201103L: std::cout << "Compiled in C++11"; break;
case 201402L: std::cout << "Compiled in C++14"; break;
case 201703L: std::cout << "Compiled in C++17"; break;
default: std::cout << "Compiled in a non-conforming C++ compiler";
}
std::cout << ", targeting Windows ";
static_assert(WINVER > 0x0500, "Windows NT and earlier are not supported.");
switch (WINVER)
{
case 0x0501: std::cout << "XP"; break;
case 0x0502: std::cout << "Server 2003"; break;
case 0x0600: std::cout << "Vista"; break;
case 0x0601: std::cout << "7"; break;
case 0x0602: std::cout << "8"; break;
case 0x0603: std::cout << "8.1"; break;
case 0x0A00: std::cout << "10"; break;
default: std::cout << "10+";
}
std::cout << "\n";
}
{
log("Testing serialization and hashing for thread::id...");
std::cout << "Serialization:\t" << this_thread::get_id() << "\n";
std::hash<thread::id> hasher;
std::cout << "Hash:\t" << hasher(this_thread::get_id()) << "\n";
}
// Regression test: Thread must copy any argument that is passed by value.
{
std::vector<std::thread> loop_threads;
std::atomic<int> i_vals_touched [4];// { 0, 0, 0, 0 };
for (int i = 0; i < 4; ++i)
i_vals_touched[i].store(0, std::memory_order_relaxed);
for (int i = 0; i < 4; ++i)
{
loop_threads.push_back(std::thread([&](int c)
{
log("For-loop test thread got value: %i", c);
i_vals_touched[c].fetch_add(1, std::memory_order_relaxed);
}, i));
}
for (std::thread & thr : loop_threads)
thr.join();
for (int i = 0; i < 4; ++i)
{
if (i_vals_touched[i] != 1)
{
log("FATAL: Threads are not copying arguments!");
return 1;
}
}
}
std::thread t([](TestMove&& a, const char* b, int c) mutable
{
try
{
log("Worker thread started, sleeping for a while...");
// Thread might move the string more than once.
assert(a.mStr.substr(0, 15) == "move test moved");
assert(!strcmp(b, "test message"));
assert(c == -20);
auto move2nd = std::move(a); //test move to final destination
this_thread::sleep_for(std::chrono::milliseconds(1000));
{
lock_guard<mutex> lock(m);
cond = 1;
log("Notifying condvar");
cv.notify_all();
}
this_thread::sleep_for(std::chrono::milliseconds(500));
{
lock_guard<decltype(sm)> lock(sm);
cond = 2;
log("Notifying condvar");
cv_any.notify_all();
}
this_thread::sleep_for(std::chrono::milliseconds(500));
{
lock_guard<decltype(sm)> lock(sm);
cond = 3;
log("Notifying condvar");
cv_any.notify_all();
}
log("Worker thread finishing");
}
catch(std::exception& e)
{
printf("EXCEPTION in worker thread: %s\n", e.what());
}
},
TestMove("move test"), "test message", -20);
try
{
log("Main thread: Locking mutex, waiting on condvar...");
{
std::unique_lock<decltype(m)> lk(m);
cv.wait(lk, []{ return cond >= 1;} );
log("condvar notified, cond = %d", cond);
assert(lk.owns_lock());
}
log("Main thread: Locking shared_mutex, waiting on condvar...");
{
std::unique_lock<decltype(sm)> lk(sm);
cv_any.wait(lk, []{ return cond >= 2;} );
log("condvar notified, cond = %d", cond);
assert(lk.owns_lock());
}
log("Main thread: Locking shared_mutex in shared mode, waiting on condvar...");
{
std::shared_lock<decltype(sm)> lk(sm);
cv_any.wait(lk, []{ return cond >= 3;} );
log("condvar notified, cond = %d", cond);
assert(lk.owns_lock());
}
log("Main thread: Waiting on worker join...");
t.join();
log("Main thread: Worker thread joined");
fflush(stdout);
}
catch(std::exception& e)
{
log("EXCEPTION in main thread: %s", e.what());
}
once_flag of;
call_once(of, test_call_once, 1, "test");
call_once(of, test_call_once, 1, "ERROR! Should not be called second time");
log("Test complete");
{
log("Testing implementation of <future>...");
test_future<int>();
test_future<void>();
test_future<int &>();
test_future<int const &>();
test_future<int volatile &>();
test_future<int const volatile &>();
log("Testing <future>'s use of allocators. Should allocate, then deallocate.");
promise<int> allocated_promise (std::allocator_arg, CustomAllocator<unsigned>());
allocated_promise.set_value(7);
}
return 0;
}

View File

@@ -0,0 +1,226 @@
<#
.SYNOPSIS
Generate std-like headers which you can use just like standard c++'s ones.
For example include <thread>.
.PARAMETER GccPath
Path to GCC. Will try to use the default one from $env:Path if not
specified.
.PARAMETER MinGWStdThreadsPath
Path to mingw-std-threads folder. Will try to use $PSScriptRoot/.. if not
specified.
.PARAMETER DestinationFolder
Destination folder where generated headers will be saved to
.PARAMETER GenerateCompilerWrapperWithFileName
If specified, will be generated a wrapper batch script for g++ which automatically
adds $DestinationFolder as an include path
.PARAMETER Interactive
Use this switch if you want to pass parameters interactively
#>
[CmdletBinding(PositionalBinding = $false)]
param (
# Path of GCC
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = "NonInteractive",
HelpMessage = "Pathtof GCC. Will try to use the default one from `$env:Path if not specified.")]
[string]
$GccPath,
# Path of mingw-std-threads
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = "NonInteractive",
HelpMessage = "Path to mingw-std-threads folder. Will try to use `$PSScriptRoot/.. if not specified.")]
[string]
$MinGWStdThreadsPath,
# Destination folder path
[Parameter(Mandatory = $true,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = "NonInteractive",
HelpMessage = "Destination folder where generated headers will be saved to")]
[ValidateNotNullOrEmpty()]
[string]
$DestinationFolder,
# Compiler wrapper path
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = "NonInteractive",
HelpMessage = "If specified, will generate a wrapper batch script for g++ which automatically adds `$DestinationFolder as an include path")]
[string]
$GenerateCompilerWrapperWithFileName,
# Interactive Switch
[Parameter(ParameterSetName = "Interactive")]
[switch]
$Interactive = $false
)
# Stop execution when encountering any error (includeing Write-Error command)
$ErrorActionPreference = "Stop";
# headers to be generated
$headers = @("condition_variable", "future", "mutex", "shared_mutex", "thread")
# ask for user input in interactive mode
if ($Interactive) {
Write-Host "Generate std-like headers which you can use just like standard c++'s ones."
Write-Host "Something like `"include <thread>`"."
$DestinationFolder = Read-Host -Prompt "Destination folder into which headers will be generated"
$GccPath = Read-Host -Prompt "Path to GCC, optional. Press Enter to let it be retrieved from PATH"
$MinGWStdThreadsPath = Read-Host -Prompt "Path to mingw-std-threads folder, optional. Press Enter to use default value"
$GenerateCompilerWrapperWithFileName = Read-Host "Optional path to which a wrapper batch script for g++ will be created. It will automatically use $DestinationFolder as an include path. Press Enter to skip"
}
if (-not $GccPath) {
$GccPath = "gcc"
}
# set default value of $MinGWStdThreadsPath
if (-not $MinGWStdThreadsPath) {
$scriptFilePath = $null
if ($MyInvocation.MyCommand.CommandType -eq "ExternalScript") {
$scriptFilePath = $MyInvocation.MyCommand.Definition
}
else {
$scriptFilePath = [Environment]::GetCommandLineArgs()[0]
}
$MinGWStdThreadsPath = (Get-Item -LiteralPath $scriptFilePath).Directory.Parent.FullName
}
# Normalize paths
$GccPath = (Get-Command -Name $GccPath).Source
$MinGWStdThreadsPath = Resolve-Path -LiteralPath $MinGWStdThreadsPath
$DestinationFolder = New-Item -Path $DestinationFolder -ItemType "Directory" -Force
Write-Output "GccPath: $GccPath"
Write-Output "MinGWStdThreadsPath: $MinGWStdThreadsPath"
Write-Output "DestinationFolder: $DestinationFolder"
if ($GenerateCompilerWrapperWithFileName) {
Write-Output "GenerateCompilerWrapperWithFileName: $GenerateCompilerWrapperWithFileName"
}
# Find path of real headers
Write-Output "Retrieving system header search paths..."
$readingIncludePath = $false
# Empty array which will later store include paths
$includePaths = @()
# Launch GCC
$processStartInfo = New-Object -TypeName "System.Diagnostics.ProcessStartInfo"
$processStartInfo.FileName = $GccPath
$processStartInfo.Arguments = "-xc++ -E -v -"
$processStartInfo.RedirectStandardInput = $true
$processStartInfo.RedirectStandardOutput = $true
$processStartInfo.RedirectStandardError = $true
$processStartInfo.UseShellExecute = $false
$outputLines = @()
$gcc = New-Object -TypeName "System.Diagnostics.Process"
try {
$gcc.StartInfo = $processStartInfo
$gcc.Start() | Out-Null
$gcc.StandardInput.Close()
$gcc.WaitForExit()
$output = $gcc.StandardError.ReadToEnd()
$outputLines = $output -split "[\r\n]" |
ForEach-Object { return $_.Trim() } |
Where-Object { return $_.Length -gt 0 }
}
finally {
$gcc.StandardInput.Dispose()
$gcc.StandardOutput.Dispose()
$gcc.StandardError.Dispose()
$gcc.Dispose()
}
# Parse Output
foreach ($line in $outputLines) {
if (-not $readingIncludePath) {
if ($line -match "#include <...> search starts here:") {
$readingIncludePath = $true
}
continue
}
if ($line -match "End of search list.") {
break
}
Write-Output "Retrieved search path: $line"
$includePaths += $line
}
if ($includePaths.Count -eq 0) {
Write-Error "Error: didn't find any #inlcude <...> search paths"
}
# look for std header paths
Write-Output "Searching for standard headers..."
$stdHeaders = @()
# set a label called "nextHeader" to allow continue with outer loop
:nextHeader foreach ($header in $headers) {
# check if mingw-std-threads headers exist
$myHeader = "mingw.$header.h"
$myHeader = Join-Path -Path $MinGWStdThreadsPath -ChildPath $myHeader
if (-not (Test-Path -LiteralPath $myHeader -PathType "Leaf")) {
Write-Error "Error: mingw-std-threads header not found: $myHeader"
}
foreach ($inludePath in $includePaths) {
$fullPath = Join-Path -Path $inludePath -ChildPath $header
if (Test-Path -LiteralPath $fullPath -PathType "Leaf") {
$fullPath = (Get-Item -LiteralPath $fullPath).FullName
$stdHeaders += $fullPath
Write-Output "Found std header: $fullPath"
# if found matching header, continue with outer loop
continue nextHeader
}
}
Write-Error "Error: didn't find $header in any search paths"
}
# generate headers
Write-Output "Generating headers..."
foreach ($stdHeader in $stdHeaders) {
$headerFileName = (Get-Item -LiteralPath $stdHeader).Name
$myHeader = "mingw.$headerFileName.h"
$myHeader = Join-Path -Path $MinGWStdThreadsPath -ChildPath $myHeader
Write-Output "Generating <$headerFileName> from $myHeader and $stdHeader..."
# both two headers should already have include guards
# but we still add a #pragma once just to be safe
$content = "#pragma once`r`n"
$content += "#include `"$stdHeader`"`r`n"
$content += "#include `"$myHeader`"`r`n";
$outputFileName = Join-Path -Path $DestinationFolder -ChildPath $headerFileName
Write-Output "Writing file: $outputFileName"
# use .NET's method to output lines to avoid UTF-8 BOM
$noBomEncoding = New-Object -TypeName "System.Text.UTF8Encoding" -ArgumentList $false
[IO.File]::WriteAllText($outputFileName, $content, $noBomEncoding)
}
$message = "Successfully generated std-like headers. Use them by adding "
$message += "`"-I$DestinationFolder`" to your compiler command line parameters"
Write-Output $message
if ($GenerateCompilerWrapperWithFileName) {
$compilerFolder = Split-Path -LiteralPath $GccPath
$compiler = Join-Path -Path $compilerFolder -ChildPath "g++"
$command = "@echo off`r`n"
$command += "$compiler %* `"-I$DestinationFolder`""
$wrapper = New-Item -Path $GenerateCompilerWrapperWithFileName -ItemType "File" -Force
# use .NET's method to output lines to avoid UTF-8 BOM
$noBomEncoding = New-Object -TypeName "System.Text.UTF8Encoding" -ArgumentList $false
[IO.File]::WriteAllText($wrapper, $command, $noBomEncoding)
Write-Output "Wrapper batch script successfully generated to $wrapper"
}

View File

@@ -0,0 +1 @@
powershell -NonInteractive -ExecutionPolicy ByPass -File %~dp0Generate-StdLikeHeaders.ps1 %*

View File

@@ -0,0 +1 @@
powershell -ExecutionPolicy ByPass -File %~dp0Generate-StdLikeHeaders.ps1 -Interactive