Loading...
Searching...
No Matches
spinRWMutex.h
1//
2// Copyright 2022 Pixar
3//
4// Licensed under the terms set forth in the LICENSE.txt file available at
5// https://openusd.org/license.
6//
7#ifndef PXR_BASE_TF_SPIN_RW_MUTEX_H
8#define PXR_BASE_TF_SPIN_RW_MUTEX_H
9
10#include "pxr/pxr.h"
11#include "pxr/base/tf/api.h"
12
13#include "pxr/base/arch/hints.h"
15
16#include <atomic>
17
18PXR_NAMESPACE_OPEN_SCOPE
19
51{
52 static constexpr int OneReader = 2;
53 static constexpr int WriterFlag = 1;
54
55public:
56
58 TfSpinRWMutex() : _lockState(0) {}
59
62 struct ScopedLock {
63
64 // Acquisition states.
65 static constexpr int NotAcquired = 0;
66 static constexpr int ReadAcquired = 1;
67 static constexpr int WriteAcquired = 2;
68
71 explicit ScopedLock(TfSpinRWMutex &m, bool write=true)
72 : _mutex(&m)
73 , _acqState(NotAcquired) {
74 Acquire(write);
75 }
76
78 ScopedLock() : _mutex(nullptr), _acqState(NotAcquired) {}
79
83 Release();
84 }
85
89 void Acquire(TfSpinRWMutex &m, bool write=true) {
90 Release();
91 _mutex = &m;
92 Acquire(write);
93 }
94
100 void Acquire(bool write=true) {
101 if (write) {
102 AcquireWrite();
103 }
104 else {
105 AcquireRead();
106 }
107 }
108
113 bool TryAcquire(TfSpinRWMutex &m, bool write=true) {
114 Release();
115 _mutex = &m;
116 return TryAcquire(write);
117 }
118
123 bool TryAcquire(bool write=true) {
124 return write ? TryAcquireWrite() : TryAcquireRead();
125 }
126
129 void Release() {
130 switch (_acqState) {
131 default:
132 case NotAcquired:
133 break;
134 case ReadAcquired:
135 _ReleaseRead();
136 break;
137 case WriteAcquired:
138 _ReleaseWrite();
139 break;
140 };
141 }
142
145 void AcquireRead() {
146 TF_DEV_AXIOM(_mutex);
147 TF_DEV_AXIOM(_acqState == NotAcquired);
148 _mutex->AcquireRead();
149 _acqState = ReadAcquired;
150 }
151
156 TF_DEV_AXIOM(_mutex);
157 TF_DEV_AXIOM(_acqState == NotAcquired);
158 if (_mutex->TryAcquireRead()) {
159 _acqState = ReadAcquired;
160 return true;
161 }
162 return false;
163 }
164
168 TF_DEV_AXIOM(_mutex);
169 TF_DEV_AXIOM(_acqState == NotAcquired);
170 _mutex->AcquireWrite();
171 _acqState = WriteAcquired;
172 }
173
178 TF_DEV_AXIOM(_mutex);
179 TF_DEV_AXIOM(_acqState == NotAcquired);
180 if (_mutex->TryAcquireWrite()) {
181 _acqState = WriteAcquired;
182 return true;
183 }
184 return false;
185 }
186
192 TF_DEV_AXIOM(_mutex);
193 TF_DEV_AXIOM(_acqState == ReadAcquired);
194 _acqState = WriteAcquired;
195 return _mutex->UpgradeToWriter();
196 }
197
204 TF_DEV_AXIOM(_mutex);
205 TF_DEV_AXIOM(_acqState == WriteAcquired);
206 _acqState = ReadAcquired;
207 return _mutex->DowngradeToReader();
208 }
209
210 private:
211
212 void _ReleaseRead() {
213 TF_DEV_AXIOM(_acqState == ReadAcquired);
214 _mutex->ReleaseRead();
215 _acqState = NotAcquired;
216 }
217
218 void _ReleaseWrite() {
219 TF_DEV_AXIOM(_acqState == WriteAcquired);
220 _mutex->ReleaseWrite();
221 _acqState = NotAcquired;
222 }
223
224 TfSpinRWMutex *_mutex;
225 int _acqState; // NotAcquired (0), ReadAcquired (1), WriteAcquired (2)
226 };
227
231 inline bool TryAcquireRead() {
232 // Optimistically increment the reader count.
233 if (ARCH_LIKELY(!(_lockState.fetch_add(OneReader) & WriterFlag))) {
234 // We incremented the reader count and observed no writer activity,
235 // we have a read lock.
236 return true;
237 }
238
239 // Otherwise there's writer activity. Undo the increment and return
240 // false.
241 _lockState -= OneReader;
242 return false;
243 }
244
248 inline void AcquireRead() {
249 while (true) {
250 if (TryAcquireRead()) {
251 return;
252 }
253 // There's writer activity. Wait to see no writer activity and
254 // retry.
255 _WaitForWriter();
256 }
257 }
258
260 inline void ReleaseRead() {
261 // Just decrement the count.
262 _lockState -= OneReader;
263 }
264
268 inline bool TryAcquireWrite() {
269 int state = _lockState.fetch_or(WriterFlag);
270 if (!(state & WriterFlag)) {
271 // We set the flag, wait for readers.
272 if (state != 0) {
273 // Wait for pending readers.
274 _WaitForReaders();
275 }
276 return true;
277 }
278 return false;
279 }
280
285 // Attempt to acquire -- if we fail then wait to see no other writer and
286 // retry.
287 while (true) {
288 if (TryAcquireWrite()) {
289 return;
290 }
291 _WaitForWriter();
292 }
293 }
294
296 inline void ReleaseWrite() {
297 _lockState &= ~WriterFlag;
298 }
299
307 // This thread owns a read lock, attempt to upgrade to write lock. If
308 // we do so without an intervening writer, return true, otherwise return
309 // false.
310 bool atomic = true;
311 while (true) {
312 int state = _lockState.fetch_or(WriterFlag);
313 if (!(state & WriterFlag)) {
314 // We set the flag, release our reader count and wait for any
315 // other pending readers.
316 if (_lockState.fetch_sub(
317 OneReader) != (OneReader | WriterFlag)) {
318 _WaitForReaders();
319 }
320 return atomic;
321 }
322 // There was other writer activity -- wait for it to clear, then
323 // retry.
324 atomic = false;
325 _WaitForWriter();
326 }
327 }
328
335 // Simultaneously add a reader count and clear the writer bit by adding
336 // (OneReader-1).
337 _lockState += (OneReader-1);
338 return true;
339 }
340
341private:
342 friend class TfBigRWMutex;
343
344 // Helpers for staged-acquire-write that BigRWMutex uses.
345 enum _StagedAcquireWriteState {
346 _StageNotAcquired,
347 _StageAcquiring,
348 _StageAcquired
349 };
350
351 // This API lets TfBigRWMutex acquire a write lock step-by-step so that it
352 // can begin acquiring write locks on several mutexes without waiting
353 // serially for pending readers to complete. Call _StagedAcquireWriteStep
354 // with _StageNotAcquired initially, and save the returned value. Continue
355 // repeatedly calling _StagedAcquireWriteStep, passing the previously
356 // returned value until this function returns _StageAcquired. At this
357 // point the write lock is acquired.
358 _StagedAcquireWriteState
359 _StagedAcquireWriteStep(_StagedAcquireWriteState curState) {
360 int state;
361 switch (curState) {
362 case _StageNotAcquired:
363 state = _lockState.fetch_or(WriterFlag);
364 if (!(state & WriterFlag)) {
365 // We set the flag. If there were no readers we're done,
366 // otherwise we'll have to wait for them, next step.
367 return state == 0 ? _StageAcquired : _StageAcquiring;
368 }
369 // Other writer activity, must retry next step.
370 return _StageNotAcquired;
371 case _StageAcquiring:
372 // We have set the writer flag but must wait to see no readers.
373 _WaitForReaders();
374 return _StageAcquired;
375 case _StageAcquired:
376 default:
377 return _StageAcquired;
378 };
379 }
380
381 TF_API void _WaitForReaders() const;
382 TF_API void _WaitForWriter() const;
383
384 std::atomic<int> _lockState;
385};
386
387PXR_NAMESPACE_CLOSE_SCOPE
388
389#endif // PXR_BASE_TF_SPIN_RW_MUTEX_H
This class implements a readers-writer mutex and provides a scoped lock utility.
Definition: bigRWMutex.h:53
This class implements a readers-writer spin lock that emphasizes throughput when there is light conte...
Definition: spinRWMutex.h:51
bool UpgradeToWriter()
Upgrade this thread's lock on this mutex (which must be a read lock) to a write lock.
Definition: spinRWMutex.h:306
bool DowngradeToReader()
Downgrade this mutex, which must be locked for write by this thread, to being locked for read by this...
Definition: spinRWMutex.h:334
bool TryAcquireRead()
Attempt to acquire a read lock on this mutex without waiting for writers.
Definition: spinRWMutex.h:231
TfSpinRWMutex()
Construct a mutex, initially unlocked.
Definition: spinRWMutex.h:58
void ReleaseRead()
Release this thread's read lock on this mutex.
Definition: spinRWMutex.h:260
bool TryAcquireWrite()
Attempt to acquire a write lock on this mutex without waiting for other writers.
Definition: spinRWMutex.h:268
void ReleaseWrite()
Release this thread's write lock on this mutex.
Definition: spinRWMutex.h:296
void AcquireWrite()
Acquire a write lock on this mutex.
Definition: spinRWMutex.h:284
void AcquireRead()
Acquire a read lock on this mutex.
Definition: spinRWMutex.h:248
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:205
Compiler hints.
Scoped lock utility class.
Definition: spinRWMutex.h:62
bool UpgradeToWriter()
Change this lock's acquisition state from a read lock to a write lock.
Definition: spinRWMutex.h:191
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:71
void Acquire(bool write=true)
Acquire either a read or write lock on this lock's associated mutex depending on write.
Definition: spinRWMutex.h:100
ScopedLock()
Construct a scoped lock not associated with a mutex.
Definition: spinRWMutex.h:78
bool DowngradeToReader()
Change this lock's acquisition state from a write lock to a read lock.
Definition: spinRWMutex.h:203
bool TryAcquireRead()
Try to acquire a read lock on this lock's associated mutex.
Definition: spinRWMutex.h:155
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:89
bool TryAcquireWrite()
Try to acquire a write lock on this lock's associated mutex.
Definition: spinRWMutex.h:177
bool TryAcquire(TfSpinRWMutex &m, bool write=true)
If the current scoped lock is acquired, Release() it, then associate this lock with m and try to acqu...
Definition: spinRWMutex.h:113
void Release()
Release the currently required lock on the associated mutex.
Definition: spinRWMutex.h:129
~ScopedLock()
If this scoped lock is acquired for either read or write, Release() it.
Definition: spinRWMutex.h:82
bool TryAcquire(bool write=true)
Try to acquire either a read or a write lock on this lock's associated mutex.
Definition: spinRWMutex.h:123
void AcquireWrite()
Acquire a write lock on this lock's associated mutex.
Definition: spinRWMutex.h:167
void AcquireRead()
Acquire a read lock on this lock's associated mutex.
Definition: spinRWMutex.h:145