olp-cpp-sdk 1.24.0
Loading...
Searching...
No Matches
shared_mutex.h
1/*
2 * Copyright (C) 2020 HERE Europe B.V.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * SPDX-License-Identifier: Apache-2.0
17 * License-Filename: LICENSE
18 */
19
20#pragma once
21
22#if ((__cplusplus >= 201703L) || (defined(_MSC_VER) && _MSC_VER >= 1900))
23#include <shared_mutex>
24#else
25
26// This implementation is taken from GCC 7.4 headers
27
28#include <cassert>
29#include <condition_variable>
30#include <system_error>
31
32#if (defined(__USE_UNIX98) || defined(__USE_XOPEN2K))
33#define HAVE_PTHREAD_RWLOCK
34#endif
35
36#ifndef THROW_OR_ABORT
37#if __cpp_exceptions
38#define THROW_OR_ABORT(exception) (throw(exception))
39#else
40#include <cstdlib>
41#define THROW_OR_ABORT(exception) \
42 do { \
43 (void)exception; \
44 std::abort(); \
45 } while (0)
46#endif
47#endif
48
49namespace std {
50namespace detail {
51
52// C++11 compatible std::exchange
53template <class T, class U = T>
54T exchange(T& obj, U&& new_value) {
55 T old_value = std::move(obj);
56 obj = std::forward<U>(new_value);
57 return old_value;
58}
59
60#if defined(HAVE_PTHREAD_RWLOCK)
61
63class shared_mutex_pthread {
64#if defined(PTHREAD_RWLOCK_INITIALIZER)
65 pthread_rwlock_t _M_rwlock = PTHREAD_RWLOCK_INITIALIZER;
66
67 public:
68 shared_mutex_pthread() = default;
69 ~shared_mutex_pthread() = default;
70#else
71 pthread_rwlock_t _M_rwlock;
72
73 public:
74 shared_mutex_pthread() {
75 int __ret = pthread_rwlock_init(&_M_rwlock, NULL);
76 if (__ret == ENOMEM)
77 THROW_OR_ABORT(std::bad_alloc());
78 else if (__ret == EAGAIN)
79 THROW_OR_ABORT(std::system_error(
80 std::make_error_code(errc::resource_unavailable_try_again)));
81 else if (__ret == EPERM)
82 THROW_OR_ABORT(std::system_error(
83 std::make_error_code(errc::operation_not_permitted)));
84 // Errors not handled: EBUSY, EINVAL
85 assert(__ret == 0);
86 }
87
88 ~shared_mutex_pthread() {
89 int __ret __attribute((__unused__)) = pthread_rwlock_destroy(&_M_rwlock);
90 // Errors not handled: EBUSY, EINVAL
91 assert(__ret == 0);
92 }
93#endif
94
95 shared_mutex_pthread(const shared_mutex_pthread&) = delete;
96 shared_mutex_pthread& operator=(const shared_mutex_pthread&) = delete;
97
98 void lock() {
99 int __ret = pthread_rwlock_wrlock(&_M_rwlock);
100 if (__ret == EDEADLK)
101 THROW_OR_ABORT(std::system_error(
102 std::make_error_code(errc::resource_deadlock_would_occur)));
103 // Errors not handled: EINVAL
104 assert(__ret == 0);
105 }
106
107 bool try_lock() {
108 int __ret = pthread_rwlock_trywrlock(&_M_rwlock);
109 if (__ret == EBUSY)
110 return false;
111 // Errors not handled: EINVAL
112 assert(__ret == 0);
113 return true;
114 }
115
116 void unlock() {
117 int __ret __attribute((__unused__)) = pthread_rwlock_unlock(&_M_rwlock);
118 // Errors not handled: EPERM, EBUSY, EINVAL
119 assert(__ret == 0);
120 }
121
122 // Shared ownership
123
124 void lock_shared() {
125 int __ret;
126 // We retry if we exceeded the maximum number of read locks supported by
127 // the POSIX implementation; this can result in busy-waiting, but this
128 // is okay based on the current specification of forward progress
129 // guarantees by the standard.
130 do {
131 __ret = pthread_rwlock_rdlock(&_M_rwlock);
132 } while (__ret == EAGAIN);
133 if (__ret == EDEADLK)
134 THROW_OR_ABORT(std::system_error(
135 std::make_error_code(errc::resource_deadlock_would_occur)));
136 // Errors not handled: EINVAL
137 assert(__ret == 0);
138 }
139
140 bool try_lock_shared() {
141 int __ret = pthread_rwlock_tryrdlock(&_M_rwlock);
142 // If the maximum number of read locks has been exceeded, we just fail
143 // to acquire the lock. Unlike for lock(), we are not allowed to throw
144 // an exception.
145 if (__ret == EBUSY || __ret == EAGAIN)
146 return false;
147 // Errors not handled: EINVAL
148 assert(__ret == 0);
149 return true;
150 }
151
152 void unlock_shared() { unlock(); }
153
154 void* native_handle() { return &_M_rwlock; }
155};
156
157#else
158
161 // Based on Howard Hinnant's reference implementation from N2406.
162
163 // The high bit of _M_state is the write-entered flag which is set to
164 // indicate a writer has taken the lock or is queuing to take the lock.
165 // The remaining bits are the count of reader locks.
166 //
167 // To take a reader lock, block on gate1 while the write-entered flag is
168 // set or the maximum number of reader locks is held, then increment the
169 // reader lock count.
170 // To release, decrement the count, then if the write-entered flag is set
171 // and the count is zero then signal gate2 to wake a queued writer,
172 // otherwise if the maximum number of reader locks was held signal gate1
173 // to wake a reader.
174 //
175 // To take a writer lock, block on gate1 while the write-entered flag is
176 // set, then set the write-entered flag to start queueing, then block on
177 // gate2 while the number of reader locks is non-zero.
178 // To release, unset the write-entered flag and signal gate1 to wake all
179 // blocked readers and writers.
180 //
181 // This means that when no reader locks are held readers and writers get
182 // equal priority. When one or more reader locks is held a writer gets
183 // priority and no more reader locks can be taken while the writer is
184 // queued.
185
186 // Only locked when accessing _M_state or waiting on condition variables.
187 mutex _M_mut;
188 // Used to block while write-entered is set or reader count at maximum.
189 condition_variable _M_gate1;
190 // Used to block queued writers while reader count is non-zero.
191 condition_variable _M_gate2;
192 // The write-entered flag and reader count.
193 unsigned _M_state;
194
195 static constexpr unsigned _S_write_entered =
196 1U << (sizeof(unsigned) * __CHAR_BIT__ - 1);
197 static constexpr unsigned _S_max_readers = ~_S_write_entered;
198
199 // Test whether the write-entered flag is set. _M_mut must be locked.
200 bool _M_write_entered() const { return _M_state & _S_write_entered; }
201
202 // The number of reader locks currently held. _M_mut must be locked.
203 unsigned _M_readers() const { return _M_state & _S_max_readers; }
204
205 public:
206 shared_mutex_cv() : _M_state(0) {}
207
208 ~shared_mutex_cv() { assert(_M_state == 0); }
209
210 shared_mutex_cv(const shared_mutex_cv&) = delete;
211 shared_mutex_cv& operator=(const shared_mutex_cv&) = delete;
212
213 // Exclusive ownership
214
216 void lock() {
217 unique_lock<mutex> __lk(_M_mut);
218 // Wait until we can set the write-entered flag.
219 _M_gate1.wait(__lk, [=] { return !_M_write_entered(); });
220 _M_state |= _S_write_entered;
221 // Then wait until there are no more readers.
222 _M_gate2.wait(__lk, [=] { return _M_readers() == 0; });
223 }
224
226 bool try_lock() {
227 unique_lock<mutex> __lk(_M_mut, try_to_lock);
228 if (__lk.owns_lock() && _M_state == 0) {
229 _M_state = _S_write_entered;
230 return true;
231 }
232 return false;
233 }
234
236 void unlock() {
237 lock_guard<mutex> __lk(_M_mut);
238 assert(_M_write_entered());
239 _M_state = 0;
240 // call notify_all() while mutex is held so that another thread can't
241 // lock and unlock the mutex then destroy *this before we make the call.
242 _M_gate1.notify_all();
243 }
244
245 // Shared ownership
246
248 void lock_shared() {
249 unique_lock<mutex> __lk(_M_mut);
250 _M_gate1.wait(__lk, [=] { return _M_state < _S_max_readers; });
251 ++_M_state;
252 }
253
256 unique_lock<mutex> __lk(_M_mut, try_to_lock);
257 if (!__lk.owns_lock())
258 return false;
259 if (_M_state < _S_max_readers) {
260 ++_M_state;
261 return true;
262 }
263 return false;
264 }
265
268 lock_guard<mutex> __lk(_M_mut);
269 assert(_M_readers() > 0);
270 auto __prev = _M_state--;
271 if (_M_write_entered()) {
272 // Wake the queued writer if there are no more readers.
273 if (_M_readers() == 0)
274 _M_gate2.notify_one();
275 // No need to notify gate1 because we give priority to the queued
276 // writer, and that writer will eventually notify gate1 after it
277 // clears the write-entered flag.
278 } else {
279 // Wake any thread that was blocked on reader overflow.
280 if (__prev == _S_max_readers)
281 _M_gate1.notify_one();
282 }
283 }
284};
285#endif
286} // namespace detail
287
293 public:
294 shared_mutex() = default;
295 ~shared_mutex() = default;
296
297 shared_mutex(const shared_mutex&) = delete;
298 shared_mutex& operator=(const shared_mutex&) = delete;
299
301 void lock() { _M_impl.lock(); }
302
308 bool try_lock() { return _M_impl.try_lock(); }
309
311 void unlock() { _M_impl.unlock(); }
312
317 void lock_shared() { _M_impl.lock_shared(); }
318
324 bool try_lock_shared() { return _M_impl.try_lock_shared(); }
325
327 void unlock_shared() { _M_impl.unlock_shared(); }
328
329#if defined(HAVE_PTHREAD_RWLOCK)
330 typedef void* native_handle_type;
331 native_handle_type native_handle() { return _M_impl.native_handle(); }
332
333 private:
334 detail::shared_mutex_pthread _M_impl;
335#else
336
337 private:
338 detail::shared_mutex_cv _M_impl;
339#endif
340};
341
342} // namespace std
343
344// `std::shared_lock` is available since C++14
345#if ((__cplusplus >= 201402L) || (defined(_MSC_VER) && _MSC_VER >= 1900))
346#include <shared_mutex>
347#else
348
349namespace std {
350
351template <typename Mutex>
356class shared_lock {
357 public:
359 typedef Mutex mutex_type;
360
361 shared_lock() noexcept : _M_pm(nullptr), _M_owns(false) {}
362
369 explicit shared_lock(mutex_type& __m)
370 : _M_pm(std::addressof(__m)), _M_owns(true) {
371 __m.lock_shared();
372 }
373
380 shared_lock(mutex_type& __m, defer_lock_t) noexcept
381 : _M_pm(std::addressof(__m)), _M_owns(false) {}
382
389 shared_lock(mutex_type& __m, try_to_lock_t)
390 : _M_pm(std::addressof(__m)), _M_owns(__m.try_lock_shared()) {}
391
398 shared_lock(mutex_type& __m, adopt_lock_t)
399 : _M_pm(std::addressof(__m)), _M_owns(true) {}
400
401 template <typename _Clock, typename _Duration>
402
410 shared_lock(mutex_type& __m,
411 const chrono::time_point<_Clock, _Duration>& __abs_time)
412 : _M_pm(std::addressof(__m)),
413 _M_owns(__m.try_lock_shared_until(__abs_time)) {}
414
415 template <typename _Rep, typename _Period>
416
424 shared_lock(mutex_type& __m,
425 const chrono::duration<_Rep, _Period>& __rel_time)
426 : _M_pm(std::addressof(__m)),
427 _M_owns(__m.try_lock_shared_for(__rel_time)) {}
428
429 ~shared_lock() {
430 if (_M_owns)
431 _M_pm->unlock_shared();
432 }
433
434 shared_lock(shared_lock const&) = delete;
435 shared_lock& operator=(shared_lock const&) = delete;
436
442 shared_lock(shared_lock&& __sl) noexcept : shared_lock() { swap(__sl); }
443
449 shared_lock& operator=(shared_lock&& __sl) noexcept {
450 shared_lock(std::move(__sl)).swap(*this);
451 return *this;
452 }
453
455 void lock() {
456 _M_lockable();
457 _M_pm->lock_shared();
458 _M_owns = true;
459 }
460
462 bool try_lock() {
463 _M_lockable();
464 return _M_owns = _M_pm->try_lock_shared();
465 }
466
467 template <typename _Rep, typename _Period>
468
477 bool try_lock_for(const chrono::duration<_Rep, _Period>& __rel_time) {
478 _M_lockable();
479 return _M_owns = _M_pm->try_lock_shared_for(__rel_time);
480 }
481
482 template <typename _Clock, typename _Duration>
483
492 bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __abs_time) {
493 _M_lockable();
494 return _M_owns = _M_pm->try_lock_shared_until(__abs_time);
495 }
496
498 void unlock() {
499 if (!_M_owns)
500 THROW_OR_ABORT(std::system_error(
501 std::make_error_code(errc::resource_deadlock_would_occur)));
502 _M_pm->unlock_shared();
503 _M_owns = false;
504 }
505
511 void swap(shared_lock& __u) noexcept {
512 std::swap(_M_pm, __u._M_pm);
513 std::swap(_M_owns, __u._M_owns);
514 }
515
522 mutex_type* release() noexcept {
523 _M_owns = false;
524 return detail::exchange(_M_pm, nullptr);
525 }
526
533 bool owns_lock() const noexcept { return _M_owns; }
534
536 explicit operator bool() const noexcept { return _M_owns; }
537
544 mutex_type* mutex() const noexcept { return _M_pm; }
545
546 private:
547 void _M_lockable() const {
548 if (_M_pm == nullptr)
549 THROW_OR_ABORT(std::system_error(
550 std::make_error_code(errc::operation_not_permitted)));
551 if (_M_owns)
552 THROW_OR_ABORT(std::system_error(
553 std::make_error_code(errc::resource_deadlock_would_occur)));
554 }
555
556 mutex_type* _M_pm;
557 bool _M_owns;
558};
559
560template <typename _Mutex>
561void swap(shared_lock<_Mutex>& __x, shared_lock<_Mutex>& __y) noexcept {
562 __x.swap(__y);
563}
564
565} // namespace std
566#endif
567
568#if defined(HAVE_PTHREAD_RWLOCK)
569#undef HAVE_PTHREAD_RWLOCK
570#endif
571
572#endif
A shared mutex type implemented using std::condition_variable.
Definition shared_mutex.h:160
void lock()
Takes ownership of the associated mutex.
Definition shared_mutex.h:216
bool try_lock()
Tries to take ownership of the mutex without blocking.
Definition shared_mutex.h:226
void lock_shared()
Blocks the calling thread until the thread obtains shared ownership of the mutex.
Definition shared_mutex.h:248
bool try_lock_shared()
Tries to take shared ownership of the mutex without blocking.
Definition shared_mutex.h:255
void unlock()
Releases the ownership of the mutex from the calling thread.
Definition shared_mutex.h:236
void unlock_shared()
Releases the shared ownership of the mutex from the calling thread.
Definition shared_mutex.h:267
A shared mutex type that can be locked exclusively by one thread or shared non-exclusively by multipl...
Definition shared_mutex.h:292
bool try_lock()
Tries to take ownership of the mutex without blocking.
Definition shared_mutex.h:308
bool try_lock_shared()
Tries to take shared ownership of the mutex without blocking.
Definition shared_mutex.h:324
void lock_shared()
Blocks the calling thread until the thread obtains shared ownership of the mutex.
Definition shared_mutex.h:317
void lock()
Takes ownership of the associated mutex.
Definition shared_mutex.h:301
void unlock_shared()
Releases the shared ownership of the mutex from the calling thread.
Definition shared_mutex.h:327
void unlock()
Releases the ownership of the mutex from the calling thread.
Definition shared_mutex.h:311