Path: blob/master/thirdparty/mingw-std-threads/mingw.shared_mutex.h
9896 views
/// \file mingw.shared_mutex.h1/// \brief Standard-compliant shared_mutex for MinGW2///3/// (c) 2017 by Nathaniel J. McClatchey, Athens OH, United States4/// \author Nathaniel J. McClatchey5///6/// \copyright Simplified (2-clause) BSD License.7///8/// \note This file may become part of the mingw-w64 runtime package. If/when9/// this happens, the appropriate license will be added, i.e. this code will10/// become dual-licensed, and the current BSD 2-clause license will stay.11/// \note Target Windows version is determined by WINVER, which is determined in12/// <windows.h> from _WIN32_WINNT, which can itself be set by the user.1314// Notes on the namespaces:15// - The implementation can be accessed directly in the namespace16// mingw_stdthread.17// - Objects will be brought into namespace std by a using directive. This18// will cause objects declared in std (such as MinGW's implementation) to19// hide this implementation's definitions.20// - To avoid poluting the namespace with implementation details, all objects21// to be pushed into std will be placed in mingw_stdthread::visible.22// The end result is that if MinGW supplies an object, it is automatically23// used. If MinGW does not supply an object, this implementation's version will24// instead be used.2526#ifndef MINGW_SHARED_MUTEX_H_27#define MINGW_SHARED_MUTEX_H_2829#if !defined(__cplusplus) || (__cplusplus < 201103L)30#error A C++11 compiler is required!31#endif3233#include <cassert>34// For descriptive errors.35#include <system_error>36// Implementing a shared_mutex without OS support will require atomic read-37// modify-write capacity.38#include <atomic>39// For timing in shared_lock and shared_timed_mutex.40#include <chrono>41#include <limits>4243// Use MinGW's shared_lock class template, if it's available. Requires C++14.44// If unavailable (eg. because this library is being used in C++11), then an45// implementation of shared_lock is provided by this header.46#if (__cplusplus >= 201402L)47#include <shared_mutex>48#endif4950// For defer_lock_t, adopt_lock_t, and try_to_lock_t51#include "mingw.mutex.h"52// For this_thread::yield.53//#include "mingw.thread.h"5455// Might be able to use native Slim Reader-Writer (SRW) locks.56#ifdef _WIN3257#include <sdkddkver.h> // Detect Windows version.58#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))59#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\60with Microsoft's API. We'll try to work around this, but we can make no\61guarantees. This problem does not exist in MinGW-w64."62#include <windows.h> // No further granularity can be expected.63#else64#include <synchapi.h>65#endif66#endif6768namespace mingw_stdthread69{70// Define a portable atomics-based shared_mutex71namespace portable72{73class shared_mutex74{75typedef uint_fast16_t counter_type;76std::atomic<counter_type> mCounter {0};77static constexpr counter_type kWriteBit = 1 << (std::numeric_limits<counter_type>::digits - 1);7879#if STDMUTEX_RECURSION_CHECKS80// Runtime checker for verifying owner threads. Note: Exclusive mode only.81_OwnerThread mOwnerThread {};82#endif83public:84typedef shared_mutex * native_handle_type;8586shared_mutex () = default;8788// No form of copying or moving should be allowed.89shared_mutex (const shared_mutex&) = delete;90shared_mutex & operator= (const shared_mutex&) = delete;9192~shared_mutex ()93{94// Terminate if someone tries to destroy an owned mutex.95assert(mCounter.load(std::memory_order_relaxed) == 0);96}9798void lock_shared (void)99{100counter_type expected = mCounter.load(std::memory_order_relaxed);101do102{103// Delay if writing or if too many readers are attempting to read.104if (expected >= kWriteBit - 1)105{106using namespace std;107expected = mCounter.load(std::memory_order_relaxed);108continue;109}110if (mCounter.compare_exchange_weak(expected,111static_cast<counter_type>(expected + 1),112std::memory_order_acquire,113std::memory_order_relaxed))114break;115}116while (true);117}118119bool try_lock_shared (void)120{121counter_type expected = mCounter.load(std::memory_order_relaxed) & static_cast<counter_type>(~kWriteBit);122if (expected + 1 == kWriteBit)123return false;124else125return mCounter.compare_exchange_strong( expected,126static_cast<counter_type>(expected + 1),127std::memory_order_acquire,128std::memory_order_relaxed);129}130131void unlock_shared (void)132{133using namespace std;134#ifndef NDEBUG135if (!(mCounter.fetch_sub(1, memory_order_release) & static_cast<counter_type>(~kWriteBit)))136__builtin_trap();137#else138mCounter.fetch_sub(1, memory_order_release);139#endif140}141142// Behavior is undefined if a lock was previously acquired.143void lock (void)144{145#if STDMUTEX_RECURSION_CHECKS146DWORD self = mOwnerThread.checkOwnerBeforeLock();147#endif148using namespace std;149// Might be able to use relaxed memory order...150// Wait for the write-lock to be unlocked, then claim the write slot.151counter_type current;152while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit);153//this_thread::yield();154// Wait for readers to finish up.155while (current != kWriteBit)156{157//this_thread::yield();158current = mCounter.load(std::memory_order_acquire);159}160#if STDMUTEX_RECURSION_CHECKS161mOwnerThread.setOwnerAfterLock(self);162#endif163}164165bool try_lock (void)166{167#if STDMUTEX_RECURSION_CHECKS168DWORD self = mOwnerThread.checkOwnerBeforeLock();169#endif170counter_type expected = 0;171bool ret = mCounter.compare_exchange_strong(expected, kWriteBit,172std::memory_order_acquire,173std::memory_order_relaxed);174#if STDMUTEX_RECURSION_CHECKS175if (ret)176mOwnerThread.setOwnerAfterLock(self);177#endif178return ret;179}180181void unlock (void)182{183#if STDMUTEX_RECURSION_CHECKS184mOwnerThread.checkSetOwnerBeforeUnlock();185#endif186using namespace std;187#ifndef NDEBUG188if (mCounter.load(memory_order_relaxed) != kWriteBit)189__builtin_trap();190#endif191mCounter.store(0, memory_order_release);192}193194native_handle_type native_handle (void)195{196return this;197}198};199200} // Namespace portable201202// The native shared_mutex implementation primarily uses features of Windows203// Vista, but the features used for try_lock and try_lock_shared were not204// introduced until Windows 7. To allow limited use while compiling for Vista,205// I define the class without try_* functions in that case.206// Only fully-featured implementations will be placed into namespace std.207#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA)208namespace vista209{210class condition_variable_any;211}212213namespace windows7214{215// We already #include "mingw.mutex.h". May as well reduce redundancy.216class shared_mutex : windows7::mutex217{218// Allow condition_variable_any (and only condition_variable_any) to treat a219// shared_mutex as its base class.220friend class vista::condition_variable_any;221public:222using windows7::mutex::native_handle_type;223using windows7::mutex::lock;224using windows7::mutex::unlock;225using windows7::mutex::native_handle;226227void lock_shared (void)228{229AcquireSRWLockShared(native_handle());230}231232void unlock_shared (void)233{234ReleaseSRWLockShared(native_handle());235}236237// TryAcquireSRW functions are a Windows 7 feature.238#if (WINVER >= _WIN32_WINNT_WIN7)239bool try_lock_shared (void)240{241return TryAcquireSRWLockShared(native_handle()) != 0;242}243244using windows7::mutex::try_lock;245#endif246};247248} // Namespace windows7249#endif // Compiling for Vista250#if (defined(_WIN32) && (WINVER >= _WIN32_WINNT_WIN7))251using windows7::shared_mutex;252#else253using portable::shared_mutex;254#endif255256class shared_timed_mutex : shared_mutex257{258typedef shared_mutex Base;259public:260using Base::lock;261using Base::try_lock;262using Base::unlock;263using Base::lock_shared;264using Base::try_lock_shared;265using Base::unlock_shared;266267template< class Clock, class Duration >268bool try_lock_until ( const std::chrono::time_point<Clock,Duration>& cutoff )269{270do271{272if (try_lock())273return true;274}275while (std::chrono::steady_clock::now() < cutoff);276return false;277}278279template< class Rep, class Period >280bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time)281{282return try_lock_until(std::chrono::steady_clock::now() + rel_time);283}284285template< class Clock, class Duration >286bool try_lock_shared_until ( const std::chrono::time_point<Clock,Duration>& cutoff )287{288do289{290if (try_lock_shared())291return true;292}293while (std::chrono::steady_clock::now() < cutoff);294return false;295}296297template< class Rep, class Period >298bool try_lock_shared_for (const std::chrono::duration<Rep,Period>& rel_time)299{300return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time);301}302};303304#if __cplusplus >= 201402L305using std::shared_lock;306#else307// If not supplied by shared_mutex (eg. because C++14 is not supported), I308// supply the various helper classes that the header should have defined.309template<class Mutex>310class shared_lock311{312Mutex * mMutex;313bool mOwns;314// Reduce code redundancy315void verify_lockable (void)316{317using namespace std;318if (mMutex == nullptr)319__builtin_trap();320if (mOwns)321__builtin_trap();322}323public:324typedef Mutex mutex_type;325326shared_lock (void) noexcept327: mMutex(nullptr), mOwns(false)328{329}330331shared_lock (shared_lock<Mutex> && other) noexcept332: mMutex(other.mutex_), mOwns(other.owns_)333{334other.mMutex = nullptr;335other.mOwns = false;336}337338explicit shared_lock (mutex_type & m)339: mMutex(&m), mOwns(true)340{341mMutex->lock_shared();342}343344shared_lock (mutex_type & m, defer_lock_t) noexcept345: mMutex(&m), mOwns(false)346{347}348349shared_lock (mutex_type & m, adopt_lock_t)350: mMutex(&m), mOwns(true)351{352}353354shared_lock (mutex_type & m, try_to_lock_t)355: mMutex(&m), mOwns(m.try_lock_shared())356{357}358359template< class Rep, class Period >360shared_lock( mutex_type& m, const std::chrono::duration<Rep,Period>& timeout_duration )361: mMutex(&m), mOwns(m.try_lock_shared_for(timeout_duration))362{363}364365template< class Clock, class Duration >366shared_lock( mutex_type& m, const std::chrono::time_point<Clock,Duration>& timeout_time )367: mMutex(&m), mOwns(m.try_lock_shared_until(timeout_time))368{369}370371shared_lock& operator= (shared_lock<Mutex> && other) noexcept372{373if (&other != this)374{375if (mOwns)376mMutex->unlock_shared();377mMutex = other.mMutex;378mOwns = other.mOwns;379other.mMutex = nullptr;380other.mOwns = false;381}382return *this;383}384385386~shared_lock (void)387{388if (mOwns)389mMutex->unlock_shared();390}391392shared_lock (const shared_lock<Mutex> &) = delete;393shared_lock& operator= (const shared_lock<Mutex> &) = delete;394395// Shared locking396void lock (void)397{398verify_lockable();399mMutex->lock_shared();400mOwns = true;401}402403bool try_lock (void)404{405verify_lockable();406mOwns = mMutex->try_lock_shared();407return mOwns;408}409410template< class Clock, class Duration >411bool try_lock_until( const std::chrono::time_point<Clock,Duration>& cutoff )412{413verify_lockable();414do415{416mOwns = mMutex->try_lock_shared();417if (mOwns)418return mOwns;419}420while (std::chrono::steady_clock::now() < cutoff);421return false;422}423424template< class Rep, class Period >425bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time)426{427return try_lock_until(std::chrono::steady_clock::now() + rel_time);428}429430void unlock (void)431{432using namespace std;433if (!mOwns)434__builtin_trap();435mMutex->unlock_shared();436mOwns = false;437}438439// Modifiers440void swap (shared_lock<Mutex> & other) noexcept441{442using namespace std;443swap(mMutex, other.mMutex);444swap(mOwns, other.mOwns);445}446447mutex_type * release (void) noexcept448{449mutex_type * ptr = mMutex;450mMutex = nullptr;451mOwns = false;452return ptr;453}454// Observers455mutex_type * mutex (void) const noexcept456{457return mMutex;458}459460bool owns_lock (void) const noexcept461{462return mOwns;463}464465explicit operator bool () const noexcept466{467return owns_lock();468}469};470471template< class Mutex >472void swap( shared_lock<Mutex>& lhs, shared_lock<Mutex>& rhs ) noexcept473{474lhs.swap(rhs);475}476#endif // C++11477} // Namespace mingw_stdthread478479namespace std480{481// Because of quirks of the compiler, the common "using namespace std;"482// directive would flatten the namespaces and introduce ambiguity where there483// was none. Direct specification (std::), however, would be unaffected.484// Take the safe option, and include only in the presence of MinGW's win32485// implementation.486#if (__cplusplus < 201703L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS) && !defined(__clang__))487using mingw_stdthread::shared_mutex;488#endif489#if (__cplusplus < 201402L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS) && !defined(__clang__))490using mingw_stdthread::shared_timed_mutex;491using mingw_stdthread::shared_lock;492#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition493#define MINGW_STDTHREAD_REDUNDANCY_WARNING494#pragma message "This version of MinGW seems to include a win32 port of\495pthreads, and probably already has C++ std threading classes implemented,\496based on pthreads. These classes, found in namespace std, are not overridden\497by the mingw-std-thread library. If you would still like to use this\498implementation (as it is more lightweight), use the classes provided in\499namespace mingw_stdthread."500#endif501} // Namespace std502#endif // MINGW_SHARED_MUTEX_H_503504505