Loading...
Searching...
No Matches
spinRWMutex.h
1//
2// Copyright 2022 Pixar
3//
4// Licensed under the Apache License, Version 2.0 (the "Apache License")
5// with the following modification; you may not use this file except in
6// compliance with the Apache License and the following modification to it:
7// Section 6. Trademarks. is deleted and replaced with:
8//
9// 6. Trademarks. This License does not grant permission to use the trade
10// names, trademarks, service marks, or product names of the Licensor
11// and its affiliates, except as required to comply with Section 4(c) of
12// the License and to reproduce the content of the NOTICE file.
13//
14// You may obtain a copy of the Apache License at
15//
16// http://www.apache.org/licenses/LICENSE-2.0
17//
18// Unless required by applicable law or agreed to in writing, software
19// distributed under the Apache License with the above modification is
20// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21// KIND, either express or implied. See the Apache License for the specific
22// language governing permissions and limitations under the Apache License.
23//
24#ifndef PXR_BASE_TF_SPIN_RW_MUTEX_H
25#define PXR_BASE_TF_SPIN_RW_MUTEX_H
26
27#include "pxr/pxr.h"
28#include "pxr/base/tf/api.h"
29
30#include "pxr/base/arch/hints.h"
32
33#include <atomic>
34
35PXR_NAMESPACE_OPEN_SCOPE
36
68{
69 static constexpr int OneReader = 2;
70 static constexpr int WriterFlag = 1;
71
72public:
73
75 TfSpinRWMutex() : _lockState(0) {}
76
79 struct ScopedLock {
80
81 // Acquisition states.
82 static constexpr int NotAcquired = 0;
83 static constexpr int ReadAcquired = 1;
84 static constexpr int WriteAcquired = 2;
85
88 explicit ScopedLock(TfSpinRWMutex &m, bool write=true)
89 : _mutex(&m)
90 , _acqState(NotAcquired) {
91 Acquire(write);
92 }
93
95 ScopedLock() : _mutex(nullptr), _acqState(NotAcquired) {}
96
100 Release();
101 }
102
106 void Acquire(TfSpinRWMutex &m, bool write=true) {
107 Release();
108 _mutex = &m;
109 Acquire(write);
110 }
111
117 void Acquire(bool write=true) {
118 if (write) {
119 AcquireWrite();
120 }
121 else {
122 AcquireRead();
123 }
124 }
125
128 void Release() {
129 switch (_acqState) {
130 default:
131 case NotAcquired:
132 break;
133 case ReadAcquired:
134 _ReleaseRead();
135 break;
136 case WriteAcquired:
137 _ReleaseWrite();
138 break;
139 };
140 }
141
144 void AcquireRead() {
145 TF_DEV_AXIOM(_acqState == NotAcquired);
146 _mutex->AcquireRead();
147 _acqState = ReadAcquired;
148 }
149
153 TF_DEV_AXIOM(_acqState == NotAcquired);
154 _mutex->AcquireWrite();
155 _acqState = WriteAcquired;
156 }
157
163 TF_DEV_AXIOM(_acqState == ReadAcquired);
164 _acqState = WriteAcquired;
165 return _mutex->UpgradeToWriter();
166 }
167
174 TF_DEV_AXIOM(_acqState == WriteAcquired);
175 _acqState = ReadAcquired;
176 return _mutex->DowngradeToReader();
177 }
178
179 private:
180
181 void _ReleaseRead() {
182 TF_DEV_AXIOM(_acqState == ReadAcquired);
183 _mutex->ReleaseRead();
184 _acqState = NotAcquired;
185 }
186
187 void _ReleaseWrite() {
188 TF_DEV_AXIOM(_acqState == WriteAcquired);
189 _mutex->ReleaseWrite();
190 _acqState = NotAcquired;
191 }
192
193 TfSpinRWMutex *_mutex;
194 int _acqState; // NotAcquired (0), ReadAcquired (1), WriteAcquired (2)
195 };
196
200 inline bool TryAcquireRead() {
201 // Optimistically increment the reader count.
202 if (ARCH_LIKELY(!(_lockState.fetch_add(OneReader) & WriterFlag))) {
203 // We incremented the reader count and observed no writer activity,
204 // we have a read lock.
205 return true;
206 }
207
208 // Otherwise there's writer activity. Undo the increment and return
209 // false.
210 _lockState -= OneReader;
211 return false;
212 }
213
217 inline void AcquireRead() {
218 while (true) {
219 if (TryAcquireRead()) {
220 return;
221 }
222 // There's writer activity. Wait to see no writer activity and
223 // retry.
224 _WaitForWriter();
225 }
226 }
227
229 inline void ReleaseRead() {
230 // Just decrement the count.
231 _lockState -= OneReader;
232 }
233
237 inline bool TryAcquireWrite() {
238 int state = _lockState.fetch_or(WriterFlag);
239 if (!(state & WriterFlag)) {
240 // We set the flag, wait for readers.
241 if (state != 0) {
242 // Wait for pending readers.
243 _WaitForReaders();
244 }
245 return true;
246 }
247 return false;
248 }
249
254 // Attempt to acquire -- if we fail then wait to see no other writer and
255 // retry.
256 while (true) {
257 if (TryAcquireWrite()) {
258 return;
259 }
260 _WaitForWriter();
261 }
262 }
263
265 inline void ReleaseWrite() {
266 _lockState &= ~WriterFlag;
267 }
268
276 // This thread owns a read lock, attempt to upgrade to write lock. If
277 // we do so without an intervening writer, return true, otherwise return
278 // false.
279 bool atomic = true;
280 while (true) {
281 int state = _lockState.fetch_or(WriterFlag);
282 if (!(state & WriterFlag)) {
283 // We set the flag, release our reader count and wait for any
284 // other pending readers.
285 if (_lockState.fetch_sub(
286 OneReader) != (OneReader | WriterFlag)) {
287 _WaitForReaders();
288 }
289 return atomic;
290 }
291 // There was other writer activity -- wait for it to clear, then
292 // retry.
293 atomic = false;
294 _WaitForWriter();
295 }
296 }
297
304 // Simultaneously add a reader count and clear the writer bit by adding
305 // (OneReader-1).
306 _lockState += (OneReader-1);
307 return true;
308 }
309
310private:
311 friend class TfBigRWMutex;
312
313 // Helpers for staged-acquire-write that BigRWMutex uses.
314 enum _StagedAcquireWriteState {
315 _StageNotAcquired,
316 _StageAcquiring,
317 _StageAcquired
318 };
319
320 // This API lets TfBigRWMutex acquire a write lock step-by-step so that it
321 // can begin acquiring write locks on several mutexes without waiting
322 // serially for pending readers to complete. Call _StagedAcquireWriteStep
323 // with _StageNotAcquired initially, and save the returned value. Continue
324 // repeatedly calling _StagedAcquireWriteStep, passing the previously
325 // returned value until this function returns _StageAcquired. At this
326 // point the write lock is acquired.
327 _StagedAcquireWriteState
328 _StagedAcquireWriteStep(_StagedAcquireWriteState curState) {
329 int state;
330 switch (curState) {
331 case _StageNotAcquired:
332 state = _lockState.fetch_or(WriterFlag);
333 if (!(state & WriterFlag)) {
334 // We set the flag. If there were no readers we're done,
335 // otherwise we'll have to wait for them, next step.
336 return state == 0 ? _StageAcquired : _StageAcquiring;
337 }
338 // Other writer activity, must retry next step.
339 return _StageNotAcquired;
340 case _StageAcquiring:
341 // We have set the writer flag but must wait to see no readers.
342 _WaitForReaders();
343 return _StageAcquired;
344 case _StageAcquired:
345 default:
346 return _StageAcquired;
347 };
348 }
349
350 TF_API void _WaitForReaders() const;
351 TF_API void _WaitForWriter() const;
352
353 std::atomic<int> _lockState;
354};
355
356PXR_NAMESPACE_CLOSE_SCOPE
357
358#endif // PXR_BASE_TF_SPIN_RW_MUTEX_H
This class implements a readers-writer mutex and provides a scoped lock utility.
Definition: bigRWMutex.h:70
This class implements a readers-writer spin lock that emphasizes throughput when there is light conte...
Definition: spinRWMutex.h:68
bool UpgradeToWriter()
Upgrade this thread's lock on this mutex (which must be a read lock) to a write lock.
Definition: spinRWMutex.h:275
bool DowngradeToReader()
Downgrade this mutex, which must be locked for write by this thread, to being locked for read by this...
Definition: spinRWMutex.h:303
bool TryAcquireRead()
Attempt to acquire a read lock on this mutex without waiting for writers.
Definition: spinRWMutex.h:200
TfSpinRWMutex()
Construct a mutex, initially unlocked.
Definition: spinRWMutex.h:75
void ReleaseRead()
Release this thread's read lock on this mutex.
Definition: spinRWMutex.h:229
bool TryAcquireWrite()
Attempt to acquire a write lock on this mutex without waiting for other writers.
Definition: spinRWMutex.h:237
void ReleaseWrite()
Release this thread's write lock on this mutex.
Definition: spinRWMutex.h:265
void AcquireWrite()
Acquire a write lock on this mutex.
Definition: spinRWMutex.h:253
void AcquireRead()
Acquire a read lock on this mutex.
Definition: spinRWMutex.h:217
Stripped down version of diagnostic.h that doesn't define std::string.
#define TF_DEV_AXIOM(cond)
The same as TF_AXIOM, but compiled only in dev builds.
Definition: diagnostic.h:222
Compiler hints.
Scoped lock utility class.
Definition: spinRWMutex.h:79
bool UpgradeToWriter()
Change this lock's acquisition state from a read lock to a write lock.
Definition: spinRWMutex.h:162
ScopedLock(TfSpinRWMutex &m, bool write=true)
Construct a scoped lock for mutex m and acquire either a read or a write lock depending on write.
Definition: spinRWMutex.h:88
void Acquire(bool write=true)
Acquire either a read or write lock on this lock's associated mutex depending on write.
Definition: spinRWMutex.h:117
ScopedLock()
Construct a scoped lock not associated with a mutex.
Definition: spinRWMutex.h:95
bool DowngradeToReader()
Change this lock's acquisition state from a write lock to a read lock.
Definition: spinRWMutex.h:173
void Acquire(TfSpinRWMutex &m, bool write=true)
If the current scoped lock is acquired, Release() it, then associate this lock with m and acquire eit...
Definition: spinRWMutex.h:106
void Release()
Release the currently required lock on the associated mutex.
Definition: spinRWMutex.h:128
~ScopedLock()
If this scoped lock is acquired for either read or write, Release() it.
Definition: spinRWMutex.h:99
void AcquireWrite()
Acquire a write lock on this lock's associated mutex.
Definition: spinRWMutex.h:152
void AcquireRead()
Acquire a read lock on this lock's associated mutex.
Definition: spinRWMutex.h:144