CellModules
mingw.shared_mutex.h
Go to the documentation of this file.
1
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 <assert.h>
35
36// Use MinGW's shared_lock class template, if it's available. Requires C++14.
37// If unavailable (eg. because this library is being used in C++11), then an
38// implementation of shared_lock is provided by this header.
39#if (__cplusplus >= 201402L)
40#include <shared_mutex>
41#else
42// For defer_lock_t, adopt_lock_t, and try_to_lock_t
43#include "mingw.mutex.h"
44#endif
45
46// For descriptive errors.
47#include <system_error>
48// Implementing a shared_mutex without OS support will require atomic read-
49// modify-write capacity.
50#include <atomic>
51// For timing in shared_lock and shared_timed_mutex.
52#include <chrono>
53
54// For this_thread::yield.
55#include "mingw.thread.h"
56
57// Might be able to use native Slim Reader-Writer (SRW) locks.
58#ifdef _WIN32
59#include <windows.h>
60#endif
61
62namespace mingw_stdthread
63{
64// Define a portable atomics-based shared_mutex
65namespace portable
66{
68{
69 typedef uint_fast16_t counter_type;
70 std::atomic<counter_type> mCounter;
71 static constexpr counter_type kWriteBit = 1 << (sizeof(counter_type) * CHAR_BIT - 1);
72public:
74
76 : mCounter(0)
77 {
78 }
79
80// No form of copying or moving should be allowed.
81 shared_mutex (const shared_mutex&) = delete;
83
85 {
86// Terminate if someone tries to destroy an owned mutex.
87 assert(mCounter.load(std::memory_order_relaxed) == 0);
88 }
89
90 void lock_shared (void)
91 {
92 counter_type expected = mCounter.load(std::memory_order_relaxed);
93 do
94 {
95// Delay if writing or if too many readers are attempting to read.
96 if (expected >= kWriteBit - 1)
97 {
98 using namespace std;
99 using namespace this_thread;
100 yield();
101 expected = mCounter.load(std::memory_order_relaxed);
102 continue;
103 }
104 if (mCounter.compare_exchange_weak(expected, expected + 1,
105 std::memory_order_acquire,
106 std::memory_order_relaxed))
107 break;
108 }
109 while (true);
110 }
111
112 bool try_lock_shared (void)
113 {
114 counter_type expected = mCounter.load(std::memory_order_relaxed) & (~kWriteBit);
115 if (expected + 1 == kWriteBit)
116 return false;
117 else
118 return mCounter.compare_exchange_strong( expected, expected + 1,
119 std::memory_order_acquire,
120 std::memory_order_relaxed);
121 }
122
123 void unlock_shared (void)
124 {
125 using namespace std;
126#ifndef NDEBUG
127 if (!(mCounter.fetch_sub(1, memory_order_release) & (~kWriteBit)))
128 throw system_error(make_error_code(errc::operation_not_permitted));
129#else
130 mCounter.fetch_sub(1, memory_order_release);
131#endif
132 }
133
134// Behavior is undefined if a lock was previously acquired.
135 void lock (void)
136 {
137 using namespace std;
138 using namespace this_thread;
139// Might be able to use relaxed memory order...
140// Wait for the write-lock to be unlocked, then claim the write slot.
141 counter_type current;
142 while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit)
143 yield();
144// Wait for readers to finish up.
145 while (current != kWriteBit)
146 {
147 yield();
148 current = mCounter.load(std::memory_order_acquire);
149 }
150 }
151
152 bool try_lock (void)
153 {
154 counter_type expected = 0;
155 return mCounter.compare_exchange_strong(expected, kWriteBit,
156 std::memory_order_acquire,
157 std::memory_order_relaxed);
158 }
159
160 void unlock (void)
161 {
162 using namespace std;
163#ifndef NDEBUG
164 if (mCounter.load(memory_order_relaxed) != kWriteBit)
165 throw system_error(make_error_code(errc::operation_not_permitted));
166#endif
167 mCounter.store(0, memory_order_release);
168 }
169
171 {
172 return this;
173 }
174};
175
176} // Namespace portable
177
178// The native shared_mutex implementation primarily uses features of Windows
179// Vista, but the features used for try_lock and try_lock_shared were not
180// introduced until Windows 7. To allow limited use while compiling for Vista,
181// I define the class without try_* functions in that case.
182// Only fully-featured implementations will be placed into namespace std.
183#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA)
184namespace windows7
185{
186class shared_mutex
187{
188 SRWLOCK mHandle;
189public:
190 typedef PSRWLOCK native_handle_type;
191
192 shared_mutex ()
193 : mHandle(SRWLOCK_INIT)
194 {
195 }
196
197 ~shared_mutex () = default;
198
199// No form of copying or moving should be allowed.
200 shared_mutex (const shared_mutex&) = delete;
201 shared_mutex & operator= (const shared_mutex&) = delete;
202
203// Behavior is undefined if a lock was previously acquired by this thread.
204 void lock (void)
205 {
206 AcquireSRWLockExclusive(&mHandle);
207 }
208
209 void lock_shared (void)
210 {
211 AcquireSRWLockShared(&mHandle);
212 }
213
214 void unlock_shared (void)
215 {
216 ReleaseSRWLockShared(&mHandle);
217 }
218
219 void unlock (void)
220 {
221 ReleaseSRWLockExclusive(&mHandle);
222 }
223
224// TryAcquireSRW functions are a Windows 7 feature.
225#if (WINVER >= _WIN32_WINNT_WIN7)
226 bool try_lock_shared (void)
227 {
228 return TryAcquireSRWLockShared(&mHandle) != 0;
229 }
230
231 bool try_lock (void)
232 {
233 return TryAcquireSRWLockExclusive(&mHandle) != 0;
234 }
235#endif
236
237 native_handle_type native_handle (void)
238 {
239 return &mHandle;
240 }
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
252{
254public:
255 using Base::lock;
256 using Base::try_lock;
257 using Base::unlock;
258 using Base::lock_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>
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
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
382 {
383 if (mOwns)
384 mMutex->unlock_shared();
385 }
386
387 shared_lock (const shared_lock<Mutex> &) = delete;
389
390// Shared locking
391 void lock (void)
392 {
394 mMutex->lock_shared();
395 mOwns = true;
396 }
397
398 bool try_lock (void)
399 {
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 {
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 {
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))
483#endif
484#if (__cplusplus < 201402L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS))
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_
std::atomic< counter_type > mCounter
shared_mutex & operator=(const shared_mutex &)=delete
static constexpr counter_type kWriteBit
shared_mutex(const shared_mutex &)=delete
shared_lock(mutex_type &m, defer_lock_t) noexcept
bool try_lock_until(const std::chrono::time_point< Clock, Duration > &cutoff)
shared_lock(const shared_lock< Mutex > &)=delete
shared_lock(mutex_type &m, const std::chrono::time_point< Clock, Duration > &timeout_time)
bool owns_lock(void) const noexcept
shared_lock(shared_lock< Mutex > &&other) noexcept
shared_lock(mutex_type &m, const std::chrono::duration< Rep, Period > &timeout_duration)
bool try_lock_for(const std::chrono::duration< Rep, Period > &rel_time)
shared_lock(mutex_type &m, adopt_lock_t)
mutex_type * release(void) noexcept
shared_lock(mutex_type &m, try_to_lock_t)
void swap(shared_lock< Mutex > &other) noexcept
mutex_type * mutex(void) const noexcept
shared_lock & operator=(shared_lock< Mutex > &&other) noexcept
bool try_lock_for(const std::chrono::duration< Rep, Period > &rel_time)
bool try_lock_until(const std::chrono::time_point< Clock, Duration > &cutoff)
bool try_lock_shared_for(const std::chrono::duration< Rep, Period > &rel_time)
bool try_lock_shared_until(const std::chrono::time_point< Clock, Duration > &cutoff)
std::mutex et al implementation for MinGW (c) 2013-2016 by Mega Limited, Auckland,...
std::thread implementation for MinGW (c) 2013-2016 by Mega Limited, Auckland, New Zealand
T * ptr(T &obj)
returns a pointer (transforms reference into pointer)
Definition: utils.hpp:32
void swap(shared_lock< Mutex > &lhs, shared_lock< Mutex > &rhs) noexcept
Provides common mathematical functions and vector operations.
Definition: std.hpp:4