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