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#include <utility>
18
19PXR_NAMESPACE_OPEN_SCOPE
20
52{
53 static constexpr int OneReader = 2;
54 static constexpr int WriterFlag = 1;
55
56public:
57
59 TfSpinRWMutex() : _lockState(0) {}
60
63 struct DeferAcquire {};
64
66 static constexpr DeferAcquire deferAcquire {};
67
70 struct ScopedLock {
71
74 explicit ScopedLock(TfSpinRWMutex &m, bool write=true)
75 : _mutex(&m)
76 , _acqState(_NotAcquired) {
77 Acquire(write);
78 }
79
84 : _mutex(&m), _acqState(_NotAcquired) {}
85
87 ScopedLock() : _mutex(nullptr), _acqState(_NotAcquired) {}
88
91 ScopedLock(ScopedLock &&other) noexcept
92 : _mutex(std::exchange(other._mutex, nullptr))
93 , _acqState(std::exchange(other._acqState, _NotAcquired)) {}
94
99 ScopedLock &operator=(ScopedLock &&other) noexcept {
100 if (this != &other) {
101 Release();
102 _mutex = std::exchange(other._mutex, nullptr);
103 _acqState = std::exchange(other._acqState, _NotAcquired);
104 }
105 return *this;
106 }
107
111 Release();
112 }
113
117 void Acquire(TfSpinRWMutex &m, bool write=true) {
118 Release();
119 _mutex = &m;
120 Acquire(write);
121 }
122
128 void Acquire(bool write=true) {
129 if (write) {
130 AcquireWrite();
131 }
132 else {
133 AcquireRead();
134 }
135 }
136
141 bool TryAcquire(TfSpinRWMutex &m, bool write=true) {
142 Release();
143 _mutex = &m;
144 return TryAcquire(write);
145 }
146
151 bool TryAcquire(bool write=true) {
152 return write ? TryAcquireWrite() : TryAcquireRead();
153 }
154
157 void Release() {
158 switch (_acqState) {
159 default:
160 case _NotAcquired:
161 break;
162 case _ReadAcquired:
163 _ReleaseRead();
164 break;
165 case _WriteAcquired:
166 _ReleaseWrite();
167 break;
168 };
169 }
170
173 void AcquireRead() {
174 TF_DEV_AXIOM(_mutex);
175 TF_DEV_AXIOM(_acqState == _NotAcquired);
176 _mutex->AcquireRead();
177 _acqState = _ReadAcquired;
178 }
179
184 TF_DEV_AXIOM(_mutex);
185 TF_DEV_AXIOM(_acqState == _NotAcquired);
186 if (_mutex->TryAcquireRead()) {
187 _acqState = _ReadAcquired;
188 return true;
189 }
190 return false;
191 }
192
196 TF_DEV_AXIOM(_mutex);
197 TF_DEV_AXIOM(_acqState == _NotAcquired);
198 _mutex->AcquireWrite();
199 _acqState = _WriteAcquired;
200 }
201
210 TF_DEV_AXIOM(_mutex);
211 TF_DEV_AXIOM(_acqState == _NotAcquired);
212 if (_mutex->TryAcquireWrite()) {
213 _acqState = _WriteAcquired;
214 return true;
215 }
216 return false;
217 }
218
225 TF_DEV_AXIOM(_mutex);
226 TF_DEV_AXIOM(_acqState == _NotAcquired);
227 if (_mutex->TryAcquireWriteIfReleased()) {
228 _acqState = _WriteAcquired;
229 return true;
230 }
231 return false;
232 }
233
239 TF_DEV_AXIOM(_mutex);
240 TF_DEV_AXIOM(_acqState == _ReadAcquired);
241 bool result = _mutex->UpgradeToWriter();
242 _acqState = _WriteAcquired;
243 return result;
244 }
245
252 TF_DEV_AXIOM(_mutex);
253 TF_DEV_AXIOM(_acqState == _WriteAcquired);
254 _acqState = _ReadAcquired;
255 return _mutex->DowngradeToReader();
256 }
257
258 private:
259
260 // Acquisition states.
261 static constexpr int _NotAcquired = 0;
262 static constexpr int _ReadAcquired = 1;
263 static constexpr int _WriteAcquired = 2;
264
265 void _ReleaseRead() {
266 TF_DEV_AXIOM(_acqState == _ReadAcquired);
267 _mutex->ReleaseRead();
268 _acqState = _NotAcquired;
269 }
270
271 void _ReleaseWrite() {
272 TF_DEV_AXIOM(_acqState == _WriteAcquired);
273 _mutex->ReleaseWrite();
274 _acqState = _NotAcquired;
275 }
276
277 TfSpinRWMutex *_mutex;
278 int _acqState; // _NotAcquired (0),
279 // _ReadAcquired (1),
280 // _WriteAcquired (2)
281 };
282
286 inline bool TryAcquireRead() {
287 // Optimistically increment the reader count.
288 if (ARCH_LIKELY(!(_lockState.fetch_add(
289 OneReader, std::memory_order_acquire) &
290 WriterFlag))) {
291 // We incremented the reader count and observed no writer activity,
292 // we have a read lock.
293 return true;
294 }
295 // Otherwise there's writer activity. Undo the increment and return
296 // false. Release ordering ensures a waiting writer can observe this
297 // reader count drop to zero.
298 _lockState.fetch_sub(OneReader, std::memory_order_release);
299 return false;
300 }
301
305 inline void AcquireRead() {
306 while (true) {
307 if (TryAcquireRead()) {
308 return;
309 }
310 // There's writer activity. Wait to see no writer activity and
311 // retry.
312 _WaitForWriter();
313 }
314 }
315
317 inline void ReleaseRead() {
318 // Release ordering ensures the writer waiting in _WaitForReaders can
319 // observe this reader count drop to zero.
320 _lockState.fetch_sub(OneReader, std::memory_order_release);
321 }
322
330 inline bool TryAcquireWrite() {
331 int state = _lockState.fetch_or(WriterFlag, std::memory_order_acquire);
332 if (!(state & WriterFlag)) {
333 // We set the flag, wait for readers.
334 if (state != 0) {
335 // Wait for pending readers.
336 _WaitForReaders();
337 }
338 return true;
339 }
340 return false;
341 }
342
348 int expected = 0;
349 return _lockState.compare_exchange_strong(
350 expected, WriterFlag,
351 std::memory_order_acquire,
352 std::memory_order_relaxed);
353 }
354
359 // Attempt to acquire -- if we fail then wait to see no other writer and
360 // retry.
361 while (true) {
362 if (TryAcquireWrite()) {
363 return;
364 }
365 _WaitForWriter();
366 }
367 }
368
370 inline void ReleaseWrite() {
371 _lockState.fetch_and(~WriterFlag, std::memory_order_release);
372 }
373
381 // This thread owns a read lock, attempt to upgrade to write lock. If
382 // we do so without an intervening writer, return true, otherwise return
383 // false.
384 const auto acquire = std::memory_order_acquire;
385 const auto release = std::memory_order_release;
386 bool atomic = true;
387 while (true) {
388 int state = _lockState.fetch_or(WriterFlag, acquire);
389 if (!(state & WriterFlag)) {
390 // We set the flag, release our reader count and wait for any
391 // other pending readers. Release ordering on the fetch_sub
392 // pairs with the acquire in _WaitForReaders.
393 if (_lockState.fetch_sub(
394 OneReader, release) != (OneReader | WriterFlag)) {
395 _WaitForReaders();
396 }
397 return atomic;
398 }
399 // There was other writer activity -- wait for it to clear, then
400 // retry.
401 atomic = false;
402 _WaitForWriter();
403 }
404 }
405
412 // Simultaneously add a reader count and clear the writer bit. Since we
413 // own the WriterFlag (=1) and we want to bump the reader count by
414 // OneReader (=2) we can achieve this by adding OneReader and then
415 // clearing WriterFlag. But that's the same as adding 2 and subtracting
416 // 1, so we can do this in a single step by just adding 1. We write
417 // this as 'OneReader - 1' so it's clear this isn't a simple increment.
418 //
419 // acq_rel: release for the write side (make protected writes visible to
420 // subsequent readers), acquire for the read side (establish
421 // happens-before for subsequent reads of protected data).
422 _lockState.fetch_add(OneReader - 1, std::memory_order_acq_rel);
423 return true;
424 }
425
426private:
427 friend class TfBigRWMutex;
428
429 // Helpers for staged-acquire-write that BigRWMutex uses.
430 enum _StagedAcquireWriteState {
431 _StageNotAcquired,
432 _StageAcquiring,
433 _StageAcquired
434 };
435
436 // This API lets TfBigRWMutex acquire a write lock step-by-step so that it
437 // can begin acquiring write locks on several mutexes without waiting
438 // serially for pending readers to complete. Call _StagedAcquireWriteStep
439 // with _StageNotAcquired initially, and save the returned value. Continue
440 // repeatedly calling _StagedAcquireWriteStep, passing the previously
441 // returned value until this function returns _StageAcquired. At this
442 // point the write lock is acquired.
443 _StagedAcquireWriteState
444 _StagedAcquireWriteStep(_StagedAcquireWriteState curState) {
445 int state;
446 switch (curState) {
447 case _StageNotAcquired:
448 state = _lockState.fetch_or(WriterFlag, std::memory_order_acquire);
449 if (!(state & WriterFlag)) {
450 // We set the flag. If there were no readers we're done,
451 // otherwise we'll have to wait for them, next step.
452 return state == 0 ? _StageAcquired : _StageAcquiring;
453 }
454 // Other writer activity, must retry next step.
455 return _StageNotAcquired;
456 case _StageAcquiring:
457 // We have set the writer flag but must wait to see no readers.
458 _WaitForReaders();
459 return _StageAcquired;
460 case _StageAcquired:
461 default:
462 return _StageAcquired;
463 };
464 }
465
466 TF_API void _WaitForReaders() const;
467 TF_API void _WaitForWriter() const;
468
469 std::atomic<int> _lockState;
470};
471
472PXR_NAMESPACE_CLOSE_SCOPE
473
474#endif // PXR_BASE_TF_SPIN_RW_MUTEX_H
475
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:52
bool UpgradeToWriter()
Upgrade this thread's lock on this mutex (which must be a read lock) to a write lock.
Definition: spinRWMutex.h:380
bool DowngradeToReader()
Downgrade this mutex, which must be locked for write by this thread, to being locked for read by this...
Definition: spinRWMutex.h:411
bool TryAcquireRead()
Attempt to acquire a read lock on this mutex without waiting for writers.
Definition: spinRWMutex.h:286
TfSpinRWMutex()
Construct a mutex, initially unlocked.
Definition: spinRWMutex.h:59
void ReleaseRead()
Release this thread's read lock on this mutex.
Definition: spinRWMutex.h:317
bool TryAcquireWrite()
Attempt to acquire a write lock on this mutex without waiting for other writers, but waiting for any ...
Definition: spinRWMutex.h:330
static constexpr DeferAcquire deferAcquire
Tag value for deferred-acquisition ScopedLock construction.
Definition: spinRWMutex.h:66
bool TryAcquireWriteIfReleased()
Attempt to acquire a write lock on this mutex only if the mutex is in the fully released state (no re...
Definition: spinRWMutex.h:347
void ReleaseWrite()
Release this thread's write lock on this mutex.
Definition: spinRWMutex.h:370
void AcquireWrite()
Acquire a write lock on this mutex.
Definition: spinRWMutex.h:358
void AcquireRead()
Acquire a read lock on this mutex.
Definition: spinRWMutex.h:305
Tag type for constructing a ScopedLock associated with a mutex but not yet acquired.
Definition: spinRWMutex.h:63
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:70
bool UpgradeToWriter()
Change this lock's acquisition state from a read lock to a write lock.
Definition: spinRWMutex.h:238
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:74
void Acquire(bool write=true)
Acquire either a read or write lock on this lock's associated mutex depending on write.
Definition: spinRWMutex.h:128
ScopedLock()
Construct a scoped lock not associated with a mutex.
Definition: spinRWMutex.h:87
ScopedLock(ScopedLock &&other) noexcept
Construct a new lock taking the other lock's mutex association and acquisition state.
Definition: spinRWMutex.h:91
bool DowngradeToReader()
Change this lock's acquisition state from a write lock to a read lock.
Definition: spinRWMutex.h:251
bool TryAcquireRead()
Try to acquire a read lock on this lock's associated mutex.
Definition: spinRWMutex.h:183
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:117
bool TryAcquireWrite()
Try to acquire a write lock on this lock's associated mutex without waiting for other writers,...
Definition: spinRWMutex.h:209
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:141
ScopedLock(TfSpinRWMutex &m, TfSpinRWMutex::DeferAcquire)
Construct a scoped lock associated with mutex m but not yet acquired.
Definition: spinRWMutex.h:83
void Release()
Release the currently required lock on the associated mutex.
Definition: spinRWMutex.h:157
bool TryAcquireWriteIfReleased()
Try to acquire a write lock on this lock's associated mutex only if the mutex is in the fully release...
Definition: spinRWMutex.h:224
ScopedLock & operator=(ScopedLock &&other) noexcept
If this is not the same object as other, Release(), take the other lock's mutex association and acqui...
Definition: spinRWMutex.h:99
~ScopedLock()
If this scoped lock is acquired for either read or write, Release() it.
Definition: spinRWMutex.h:110
bool TryAcquire(bool write=true)
Try to acquire either a read or a write lock on this lock's associated mutex.
Definition: spinRWMutex.h:151
void AcquireWrite()
Acquire a write lock on this lock's associated mutex.
Definition: spinRWMutex.h:195
void AcquireRead()
Acquire a read lock on this lock's associated mutex.
Definition: spinRWMutex.h:173