All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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
111 void Release() {
112 switch (_acqState) {
113 default:
114 case NotAcquired:
115 break;
116 case ReadAcquired:
117 _ReleaseRead();
118 break;
119 case WriteAcquired:
120 _ReleaseWrite();
121 break;
122 };
123 }
124
127 void AcquireRead() {
128 TF_DEV_AXIOM(_acqState == NotAcquired);
129 _mutex->AcquireRead();
130 _acqState = ReadAcquired;
131 }
132
136 TF_DEV_AXIOM(_acqState == NotAcquired);
137 _mutex->AcquireWrite();
138 _acqState = WriteAcquired;
139 }
140
146 TF_DEV_AXIOM(_acqState == ReadAcquired);
147 _acqState = WriteAcquired;
148 return _mutex->UpgradeToWriter();
149 }
150
157 TF_DEV_AXIOM(_acqState == WriteAcquired);
158 _acqState = ReadAcquired;
159 return _mutex->DowngradeToReader();
160 }
161
162 private:
163
164 void _ReleaseRead() {
165 TF_DEV_AXIOM(_acqState == ReadAcquired);
166 _mutex->ReleaseRead();
167 _acqState = NotAcquired;
168 }
169
170 void _ReleaseWrite() {
171 TF_DEV_AXIOM(_acqState == WriteAcquired);
172 _mutex->ReleaseWrite();
173 _acqState = NotAcquired;
174 }
175
176 TfSpinRWMutex *_mutex;
177 int _acqState; // NotAcquired (0), ReadAcquired (1), WriteAcquired (2)
178 };
179
183 inline bool TryAcquireRead() {
184 // Optimistically increment the reader count.
185 if (ARCH_LIKELY(!(_lockState.fetch_add(OneReader) & WriterFlag))) {
186 // We incremented the reader count and observed no writer activity,
187 // we have a read lock.
188 return true;
189 }
190
191 // Otherwise there's writer activity. Undo the increment and return
192 // false.
193 _lockState -= OneReader;
194 return false;
195 }
196
200 inline void AcquireRead() {
201 while (true) {
202 if (TryAcquireRead()) {
203 return;
204 }
205 // There's writer activity. Wait to see no writer activity and
206 // retry.
207 _WaitForWriter();
208 }
209 }
210
212 inline void ReleaseRead() {
213 // Just decrement the count.
214 _lockState -= OneReader;
215 }
216
220 inline bool TryAcquireWrite() {
221 int state = _lockState.fetch_or(WriterFlag);
222 if (!(state & WriterFlag)) {
223 // We set the flag, wait for readers.
224 if (state != 0) {
225 // Wait for pending readers.
226 _WaitForReaders();
227 }
228 return true;
229 }
230 return false;
231 }
232
237 // Attempt to acquire -- if we fail then wait to see no other writer and
238 // retry.
239 while (true) {
240 if (TryAcquireWrite()) {
241 return;
242 }
243 _WaitForWriter();
244 }
245 }
246
248 inline void ReleaseWrite() {
249 _lockState &= ~WriterFlag;
250 }
251
259 // This thread owns a read lock, attempt to upgrade to write lock. If
260 // we do so without an intervening writer, return true, otherwise return
261 // false.
262 bool atomic = true;
263 while (true) {
264 int state = _lockState.fetch_or(WriterFlag);
265 if (!(state & WriterFlag)) {
266 // We set the flag, release our reader count and wait for any
267 // other pending readers.
268 if (_lockState.fetch_sub(
269 OneReader) != (OneReader | WriterFlag)) {
270 _WaitForReaders();
271 }
272 return atomic;
273 }
274 // There was other writer activity -- wait for it to clear, then
275 // retry.
276 atomic = false;
277 _WaitForWriter();
278 }
279 }
280
287 // Simultaneously add a reader count and clear the writer bit by adding
288 // (OneReader-1).
289 _lockState += (OneReader-1);
290 return true;
291 }
292
293private:
294 friend class TfBigRWMutex;
295
296 // Helpers for staged-acquire-write that BigRWMutex uses.
297 enum _StagedAcquireWriteState {
298 _StageNotAcquired,
299 _StageAcquiring,
300 _StageAcquired
301 };
302
303 // This API lets TfBigRWMutex acquire a write lock step-by-step so that it
304 // can begin acquiring write locks on several mutexes without waiting
305 // serially for pending readers to complete. Call _StagedAcquireWriteStep
306 // with _StageNotAcquired initially, and save the returned value. Continue
307 // repeatedly calling _StagedAcquireWriteStep, passing the previously
308 // returned value until this function returns _StageAcquired. At this
309 // point the write lock is acquired.
310 _StagedAcquireWriteState
311 _StagedAcquireWriteStep(_StagedAcquireWriteState curState) {
312 int state;
313 switch (curState) {
314 case _StageNotAcquired:
315 state = _lockState.fetch_or(WriterFlag);
316 if (!(state & WriterFlag)) {
317 // We set the flag. If there were no readers we're done,
318 // otherwise we'll have to wait for them, next step.
319 return state == 0 ? _StageAcquired : _StageAcquiring;
320 }
321 // Other writer activity, must retry next step.
322 return _StageNotAcquired;
323 case _StageAcquiring:
324 // We have set the writer flag but must wait to see no readers.
325 _WaitForReaders();
326 return _StageAcquired;
327 case _StageAcquired:
328 default:
329 return _StageAcquired;
330 };
331 }
332
333 TF_API void _WaitForReaders() const;
334 TF_API void _WaitForWriter() const;
335
336 std::atomic<int> _lockState;
337};
338
339PXR_NAMESPACE_CLOSE_SCOPE
340
341#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:258
bool DowngradeToReader()
Downgrade this mutex, which must be locked for write by this thread, to being locked for read by this...
Definition: spinRWMutex.h:286
bool TryAcquireRead()
Attempt to acquire a read lock on this mutex without waiting for writers.
Definition: spinRWMutex.h:183
TfSpinRWMutex()
Construct a mutex, initially unlocked.
Definition: spinRWMutex.h:58
void ReleaseRead()
Release this thread's read lock on this mutex.
Definition: spinRWMutex.h:212
bool TryAcquireWrite()
Attempt to acquire a write lock on this mutex without waiting for other writers.
Definition: spinRWMutex.h:220
void ReleaseWrite()
Release this thread's write lock on this mutex.
Definition: spinRWMutex.h:248
void AcquireWrite()
Acquire a write lock on this mutex.
Definition: spinRWMutex.h:236
void AcquireRead()
Acquire a read lock on this mutex.
Definition: spinRWMutex.h:200
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:145
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:156
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
void Release()
Release the currently required lock on the associated mutex.
Definition: spinRWMutex.h:111
~ScopedLock()
If this scoped lock is acquired for either read or write, Release() it.
Definition: spinRWMutex.h:82
void AcquireWrite()
Acquire a write lock on this lock's associated mutex.
Definition: spinRWMutex.h:135
void AcquireRead()
Acquire a read lock on this lock's associated mutex.
Definition: spinRWMutex.h:127