1 | // Copyright 2020 Google LLC |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // https://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | #ifndef dap_rwmutex_h |
16 | #define dap_rwmutex_h |
17 | |
18 | #include <condition_variable> |
19 | #include <mutex> |
20 | |
21 | namespace dap { |
22 | |
23 | //////////////////////////////////////////////////////////////////////////////// |
24 | // RWMutex |
25 | //////////////////////////////////////////////////////////////////////////////// |
26 | |
27 | // A RWMutex is a reader/writer mutual exclusion lock. |
28 | // The lock can be held by an arbitrary number of readers or a single writer. |
29 | // Also known as a shared mutex. |
30 | class RWMutex { |
31 | public: |
32 | inline RWMutex() = default; |
33 | |
34 | // lockReader() locks the mutex for reading. |
35 | // Multiple read locks can be held while there are no writer locks. |
36 | inline void lockReader(); |
37 | |
38 | // unlockReader() unlocks the mutex for reading. |
39 | inline void unlockReader(); |
40 | |
41 | // lockWriter() locks the mutex for writing. |
42 | // If the lock is already locked for reading or writing, lockWriter blocks |
43 | // until the lock is available. |
44 | inline void lockWriter(); |
45 | |
46 | // unlockWriter() unlocks the mutex for writing. |
47 | inline void unlockWriter(); |
48 | |
49 | private: |
50 | RWMutex(const RWMutex&) = delete; |
51 | RWMutex& operator=(const RWMutex&) = delete; |
52 | |
53 | int readLocks = 0; |
54 | int pendingWriteLocks = 0; |
55 | std::mutex mutex; |
56 | std::condition_variable cv; |
57 | }; |
58 | |
59 | void RWMutex::lockReader() { |
60 | std::unique_lock<std::mutex> lock(mutex); |
61 | readLocks++; |
62 | } |
63 | |
64 | void RWMutex::unlockReader() { |
65 | std::unique_lock<std::mutex> lock(mutex); |
66 | readLocks--; |
67 | if (readLocks == 0 && pendingWriteLocks > 0) { |
68 | cv.notify_one(); |
69 | } |
70 | } |
71 | |
72 | void RWMutex::lockWriter() { |
73 | std::unique_lock<std::mutex> lock(mutex); |
74 | if (readLocks > 0) { |
75 | pendingWriteLocks++; |
76 | cv.wait(lock, [&] { return readLocks == 0; }); |
77 | pendingWriteLocks--; |
78 | } |
79 | lock.release(); // Keep lock held |
80 | } |
81 | |
82 | void RWMutex::unlockWriter() { |
83 | if (pendingWriteLocks > 0) { |
84 | cv.notify_one(); |
85 | } |
86 | mutex.unlock(); |
87 | } |
88 | |
89 | //////////////////////////////////////////////////////////////////////////////// |
90 | // RLock |
91 | //////////////////////////////////////////////////////////////////////////////// |
92 | |
93 | // RLock is a RAII read lock helper for a RWMutex. |
94 | class RLock { |
95 | public: |
96 | inline RLock(RWMutex& mutex); |
97 | inline ~RLock(); |
98 | |
99 | inline RLock(RLock&&); |
100 | inline RLock& operator=(RLock&&); |
101 | |
102 | private: |
103 | RLock(const RLock&) = delete; |
104 | RLock& operator=(const RLock&) = delete; |
105 | |
106 | RWMutex* m; |
107 | }; |
108 | |
109 | RLock::RLock(RWMutex& mutex) : m(&mutex) { |
110 | m->lockReader(); |
111 | } |
112 | |
113 | RLock::~RLock() { |
114 | if (m != nullptr) { |
115 | m->unlockReader(); |
116 | } |
117 | } |
118 | |
119 | RLock::RLock(RLock&& other) { |
120 | m = other.m; |
121 | other.m = nullptr; |
122 | } |
123 | |
124 | RLock& RLock::operator=(RLock&& other) { |
125 | m = other.m; |
126 | other.m = nullptr; |
127 | return *this; |
128 | } |
129 | |
130 | //////////////////////////////////////////////////////////////////////////////// |
131 | // WLock |
132 | //////////////////////////////////////////////////////////////////////////////// |
133 | |
134 | // WLock is a RAII write lock helper for a RWMutex. |
135 | class WLock { |
136 | public: |
137 | inline WLock(RWMutex& mutex); |
138 | inline ~WLock(); |
139 | |
140 | inline WLock(WLock&&); |
141 | inline WLock& operator=(WLock&&); |
142 | |
143 | private: |
144 | WLock(const WLock&) = delete; |
145 | WLock& operator=(const WLock&) = delete; |
146 | |
147 | RWMutex* m; |
148 | }; |
149 | |
150 | WLock::WLock(RWMutex& mutex) : m(&mutex) { |
151 | m->lockWriter(); |
152 | } |
153 | |
154 | WLock::~WLock() { |
155 | if (m != nullptr) { |
156 | m->unlockWriter(); |
157 | } |
158 | } |
159 | |
160 | WLock::WLock(WLock&& other) { |
161 | m = other.m; |
162 | other.m = nullptr; |
163 | } |
164 | |
165 | WLock& WLock::operator=(WLock&& other) { |
166 | m = other.m; |
167 | other.m = nullptr; |
168 | return *this; |
169 | } |
170 | } // namespace dap |
171 | |
172 | #endif |
173 | |