1/// \file mingw.shared_mutex.h 2/// \brief Standard-compliant shared_mutex for MinGW 3/// 4/// (c) 2017 by Nathaniel J. McClatchey, Athens OH, United States 5/// \author Nathaniel J. McClatchey 6/// 7/// \copyright Simplified (2-clause) BSD License. 8/// 9/// \note This file may become part of the mingw-w64 runtime package. If/when 10/// this happens, the appropriate license will be added, i.e. this code will 11/// become dual-licensed, and the current BSD 2-clause license will stay. 12/// \note Target Windows version is determined by WINVER, which is determined in 13/// <windows.h> from _WIN32_WINNT, which can itself be set by the user. 14 15// Notes on the namespaces: 16// - The implementation can be accessed directly in the namespace 17// mingw_stdthread. 18// - Objects will be brought into namespace std by a using directive. This 19// will cause objects declared in std (such as MinGW's implementation) to 20// hide this implementation's definitions. 21// - To avoid poluting the namespace with implementation details, all objects 22// to be pushed into std will be placed in mingw_stdthread::visible. 23// The end result is that if MinGW supplies an object, it is automatically 24// used. If MinGW does not supply an object, this implementation's version will 25// instead be used. 26 27#ifndef MINGW_SHARED_MUTEX_H_ 28#define MINGW_SHARED_MUTEX_H_ 29 30#if !defined(__cplusplus) || (__cplusplus < 201103L) 31#error A C++11 compiler is required! 32#endif 33 34#include <cassert> 35// For descriptive errors. 36#include <system_error> 37// Implementing a shared_mutex without OS support will require atomic read- 38// modify-write capacity. 39#include <atomic> 40// For timing in shared_lock and shared_timed_mutex. 41#include <chrono> 42#include <limits> 43 44// Use MinGW's shared_lock class template, if it's available. Requires C++14. 45// If unavailable (eg. because this library is being used in C++11), then an 46// implementation of shared_lock is provided by this header. 47#if (__cplusplus >= 201402L) 48#include_next <shared_mutex> 49#endif 50 51// For defer_lock_t, adopt_lock_t, and try_to_lock_t 52#include <mutex> 53// For this_thread::yield. 54#include <thread> 55 56// Might be able to use native Slim Reader-Writer (SRW) locks. 57#ifdef _WIN32 58#include <windows.h> 59#endif 60 61namespace mingw_stdthread 62{ 63// Define a portable atomics-based shared_mutex 64namespace portable 65{ 66class shared_mutex 67{ 68 typedef uint_fast16_t counter_type; 69 std::atomic<counter_type> mCounter {0}; 70 static constexpr counter_type kWriteBit = 1 << (std::numeric_limits<counter_type>::digits - 1); 71 72#if STDMUTEX_RECURSION_CHECKS 73// Runtime checker for verifying owner threads. Note: Exclusive mode only. 74 _OwnerThread mOwnerThread {}; 75#endif 76public: 77 typedef shared_mutex * native_handle_type; 78 79 shared_mutex () = default; 80 81// No form of copying or moving should be allowed. 82 shared_mutex (const shared_mutex&) = delete; 83 shared_mutex & operator= (const shared_mutex&) = delete; 84 85 ~shared_mutex () 86 { 87// Terminate if someone tries to destroy an owned mutex. 88 assert(mCounter.load(std::memory_order_relaxed) == 0); 89 } 90 91 void lock_shared (void) 92 { 93 counter_type expected = mCounter.load(std::memory_order_relaxed); 94 do 95 { 96// Delay if writing or if too many readers are attempting to read. 97 if (expected >= kWriteBit - 1) 98 { 99 using namespace std; 100 using namespace this_thread; 101 yield(); 102 expected = mCounter.load(std::memory_order_relaxed); 103 continue; 104 } 105 if (mCounter.compare_exchange_weak(expected, 106 static_cast<counter_type>(expected + 1), 107 std::memory_order_acquire, 108 std::memory_order_relaxed)) 109 break; 110 } 111 while (true); 112 } 113 114 bool try_lock_shared (void) 115 { 116 counter_type expected = mCounter.load(std::memory_order_relaxed) & static_cast<counter_type>(~kWriteBit); 117 if (expected + 1 == kWriteBit) 118 return false; 119 else 120 return mCounter.compare_exchange_strong( expected, 121 static_cast<counter_type>(expected + 1), 122 std::memory_order_acquire, 123 std::memory_order_relaxed); 124 } 125 126 void unlock_shared (void) 127 { 128 using namespace std; 129#ifndef NDEBUG 130 if (!(mCounter.fetch_sub(1, memory_order_release) & static_cast<counter_type>(~kWriteBit))) 131 throw system_error(make_error_code(errc::operation_not_permitted)); 132#else 133 mCounter.fetch_sub(1, memory_order_release); 134#endif 135 } 136 137// Behavior is undefined if a lock was previously acquired. 138 void lock (void) 139 { 140#if STDMUTEX_RECURSION_CHECKS 141 DWORD self = mOwnerThread.checkOwnerBeforeLock(); 142#endif 143 using namespace std; 144// Might be able to use relaxed memory order... 145// Wait for the write-lock to be unlocked, then claim the write slot. 146 counter_type current; 147 while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit) 148 this_thread::yield(); 149// Wait for readers to finish up. 150 while (current != kWriteBit) 151 { 152 this_thread::yield(); 153 current = mCounter.load(std::memory_order_acquire); 154 } 155#if STDMUTEX_RECURSION_CHECKS 156 mOwnerThread.setOwnerAfterLock(self); 157#endif 158 } 159 160 bool try_lock (void) 161 { 162#if STDMUTEX_RECURSION_CHECKS 163 DWORD self = mOwnerThread.checkOwnerBeforeLock(); 164#endif 165 counter_type expected = 0; 166 bool ret = mCounter.compare_exchange_strong(expected, kWriteBit, 167 std::memory_order_acquire, 168 std::memory_order_relaxed); 169#if STDMUTEX_RECURSION_CHECKS 170 if (ret) 171 mOwnerThread.setOwnerAfterLock(self); 172#endif 173 return ret; 174 } 175 176 void unlock (void) 177 { 178#if STDMUTEX_RECURSION_CHECKS 179 mOwnerThread.checkSetOwnerBeforeUnlock(); 180#endif 181 using namespace std; 182#ifndef NDEBUG 183 if (mCounter.load(memory_order_relaxed) != kWriteBit) 184 throw system_error(make_error_code(errc::operation_not_permitted)); 185#endif 186 mCounter.store(0, memory_order_release); 187 } 188 189 native_handle_type native_handle (void) 190 { 191 return this; 192 } 193}; 194 195} // Namespace portable 196 197// The native shared_mutex implementation primarily uses features of Windows 198// Vista, but the features used for try_lock and try_lock_shared were not 199// introduced until Windows 7. To allow limited use while compiling for Vista, 200// I define the class without try_* functions in that case. 201// Only fully-featured implementations will be placed into namespace std. 202#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA) 203namespace vista 204{ 205class condition_variable_any; 206} 207 208namespace windows7 209{ 210// We already #include "mingw.mutex.h". May as well reduce redundancy. 211class shared_mutex : windows7::mutex 212{ 213// Allow condition_variable_any (and only condition_variable_any) to treat a 214// shared_mutex as its base class. 215 friend class vista::condition_variable_any; 216public: 217 using windows7::mutex::native_handle_type; 218 using windows7::mutex::lock; 219 using windows7::mutex::unlock; 220 using windows7::mutex::native_handle; 221 222 void lock_shared (void) 223 { 224 AcquireSRWLockShared(native_handle()); 225 } 226 227 void unlock_shared (void) 228 { 229 ReleaseSRWLockShared(native_handle()); 230 } 231 232// TryAcquireSRW functions are a Windows 7 feature. 233#if (WINVER >= _WIN32_WINNT_WIN7) 234 bool try_lock_shared (void) 235 { 236 return TryAcquireSRWLockShared(native_handle()) != 0; 237 } 238 239 using windows7::mutex::try_lock; 240#endif 241}; 242 243} // Namespace windows7 244#endif // Compiling for Vista 245#if (defined(_WIN32) && (WINVER >= _WIN32_WINNT_WIN7)) 246using windows7::shared_mutex; 247#else 248using portable::shared_mutex; 249#endif 250 251class shared_timed_mutex : shared_mutex 252{ 253 typedef shared_mutex Base; 254public: 255 using Base::lock; 256 using Base::try_lock; 257 using Base::unlock; 258 using Base::lock_shared; 259 using Base::try_lock_shared; 260 using Base::unlock_shared; 261 262 template< class Clock, class Duration > 263 bool try_lock_until ( const std::chrono::time_point<Clock,Duration>& cutoff ) 264 { 265 do 266 { 267 if (try_lock()) 268 return true; 269 } 270 while (std::chrono::steady_clock::now() < cutoff); 271 return false; 272 } 273 274 template< class Rep, class Period > 275 bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time) 276 { 277 return try_lock_until(std::chrono::steady_clock::now() + rel_time); 278 } 279 280 template< class Clock, class Duration > 281 bool try_lock_shared_until ( const std::chrono::time_point<Clock,Duration>& cutoff ) 282 { 283 do 284 { 285 if (try_lock_shared()) 286 return true; 287 } 288 while (std::chrono::steady_clock::now() < cutoff); 289 return false; 290 } 291 292 template< class Rep, class Period > 293 bool try_lock_shared_for (const std::chrono::duration<Rep,Period>& rel_time) 294 { 295 return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time); 296 } 297}; 298 299#if __cplusplus >= 201402L 300using std::shared_lock; 301#else 302// If not supplied by shared_mutex (eg. because C++14 is not supported), I 303// supply the various helper classes that the header should have defined. 304template<class Mutex> 305class shared_lock 306{ 307 Mutex * mMutex; 308 bool mOwns; 309// Reduce code redundancy 310 void verify_lockable (void) 311 { 312 using namespace std; 313 if (mMutex == nullptr) 314 throw system_error(make_error_code(errc::operation_not_permitted)); 315 if (mOwns) 316 throw system_error(make_error_code(errc::resource_deadlock_would_occur)); 317 } 318public: 319 typedef Mutex mutex_type; 320 321 shared_lock (void) noexcept 322 : mMutex(nullptr), mOwns(false) 323 { 324 } 325 326 shared_lock (shared_lock<Mutex> && other) noexcept 327 : mMutex(other.mutex_), mOwns(other.owns_) 328 { 329 other.mMutex = nullptr; 330 other.mOwns = false; 331 } 332 333 explicit shared_lock (mutex_type & m) 334 : mMutex(&m), mOwns(true) 335 { 336 mMutex->lock_shared(); 337 } 338 339 shared_lock (mutex_type & m, defer_lock_t) noexcept 340 : mMutex(&m), mOwns(false) 341 { 342 } 343 344 shared_lock (mutex_type & m, adopt_lock_t) 345 : mMutex(&m), mOwns(true) 346 { 347 } 348 349 shared_lock (mutex_type & m, try_to_lock_t) 350 : mMutex(&m), mOwns(m.try_lock_shared()) 351 { 352 } 353 354 template< class Rep, class Period > 355 shared_lock( mutex_type& m, const std::chrono::duration<Rep,Period>& timeout_duration ) 356 : mMutex(&m), mOwns(m.try_lock_shared_for(timeout_duration)) 357 { 358 } 359 360 template< class Clock, class Duration > 361 shared_lock( mutex_type& m, const std::chrono::time_point<Clock,Duration>& timeout_time ) 362 : mMutex(&m), mOwns(m.try_lock_shared_until(timeout_time)) 363 { 364 } 365 366 shared_lock& operator= (shared_lock<Mutex> && other) noexcept 367 { 368 if (&other != this) 369 { 370 if (mOwns) 371 mMutex->unlock_shared(); 372 mMutex = other.mMutex; 373 mOwns = other.mOwns; 374 other.mMutex = nullptr; 375 other.mOwns = false; 376 } 377 return *this; 378 } 379 380 381 ~shared_lock (void) 382 { 383 if (mOwns) 384 mMutex->unlock_shared(); 385 } 386 387 shared_lock (const shared_lock<Mutex> &) = delete; 388 shared_lock& operator= (const shared_lock<Mutex> &) = delete; 389 390// Shared locking 391 void lock (void) 392 { 393 verify_lockable(); 394 mMutex->lock_shared(); 395 mOwns = true; 396 } 397 398 bool try_lock (void) 399 { 400 verify_lockable(); 401 mOwns = mMutex->try_lock_shared(); 402 return mOwns; 403 } 404 405 template< class Clock, class Duration > 406 bool try_lock_until( const std::chrono::time_point<Clock,Duration>& cutoff ) 407 { 408 verify_lockable(); 409 do 410 { 411 mOwns = mMutex->try_lock_shared(); 412 if (mOwns) 413 return mOwns; 414 } 415 while (std::chrono::steady_clock::now() < cutoff); 416 return false; 417 } 418 419 template< class Rep, class Period > 420 bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time) 421 { 422 return try_lock_until(std::chrono::steady_clock::now() + rel_time); 423 } 424 425 void unlock (void) 426 { 427 using namespace std; 428 if (!mOwns) 429 throw system_error(make_error_code(errc::operation_not_permitted)); 430 mMutex->unlock_shared(); 431 mOwns = false; 432 } 433 434// Modifiers 435 void swap (shared_lock<Mutex> & other) noexcept 436 { 437 using namespace std; 438 swap(mMutex, other.mMutex); 439 swap(mOwns, other.mOwns); 440 } 441 442 mutex_type * release (void) noexcept 443 { 444 mutex_type * ptr = mMutex; 445 mMutex = nullptr; 446 mOwns = false; 447 return ptr; 448 } 449// Observers 450 mutex_type * mutex (void) const noexcept 451 { 452 return mMutex; 453 } 454 455 bool owns_lock (void) const noexcept 456 { 457 return mOwns; 458 } 459 460 explicit operator bool () const noexcept 461 { 462 return owns_lock(); 463 } 464}; 465 466template< class Mutex > 467void swap( shared_lock<Mutex>& lhs, shared_lock<Mutex>& rhs ) noexcept 468{ 469 lhs.swap(rhs); 470} 471#endif // C++11 472} // Namespace mingw_stdthread 473 474namespace std 475{ 476// Because of quirks of the compiler, the common "using namespace std;" 477// directive would flatten the namespaces and introduce ambiguity where there 478// was none. Direct specification (std::), however, would be unaffected. 479// Take the safe option, and include only in the presence of MinGW's win32 480// implementation. 481#if (__cplusplus < 201703L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)) 482using mingw_stdthread::shared_mutex; 483#endif 484#if (__cplusplus < 201402L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)) 485using mingw_stdthread::shared_timed_mutex; 486using mingw_stdthread::shared_lock; 487#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition 488#define MINGW_STDTHREAD_REDUNDANCY_WARNING 489#pragma message "This version of MinGW seems to include a win32 port of\ 490 pthreads, and probably already has C++ std threading classes implemented,\ 491 based on pthreads. These classes, found in namespace std, are not overridden\ 492 by the mingw-std-thread library. If you would still like to use this\ 493 implementation (as it is more lightweight), use the classes provided in\ 494 namespace mingw_stdthread." 495#endif 496} // Namespace std 497#endif // MINGW_SHARED_MUTEX_H_ 498