HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
spinRWMutex.h
Go to the documentation of this file.
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 
19 
20 /// \class TfSpinRWMutex
21 ///
22 /// This class implements a readers-writer spin lock that emphasizes throughput
23 /// when there is light contention or moderate contention dominated by readers.
24 /// Like all spin locks, significant contention performs poorly; consider a
25 /// different algorithm design or synchronization strategy in that case.
26 ///
27 /// In the best case, acquiring a read lock is an atomic add followed by a
28 /// conditional branch, and acquiring a write lock is an atomic bitwise-or
29 /// followed by a conditional branch.
30 ///
31 /// When contended by only readers, acquiring a read lock is the same: an atomic
32 /// add followed by a conditional branch. Of course the shared cache line being
33 /// concurrently read and modified will affect performance.
34 ///
35 /// In the worst case, acquiring a read lock does the atomic add and conditional
36 /// branch, but the condition shows writer activity, so the add must be undone
37 /// by a subtraction, and then the thread must wait to see no writer activity
38 /// before trying again.
39 ///
40 /// Similarly in the worst case for acquiring a write lock, the thread does the
41 /// atomic bitwise-or, but sees another active writer, and then must wait to see
42 /// no writer activity before trying again. Once the exclusive-or is done
43 /// successfully, then the writer must wait for any pending readers to clear out
44 /// before it can proceed.
45 ///
46 /// This class provides a nested TfSpinRWMutex::ScopedLock that makes it easy to
47 /// acquire locks, upgrade reader to writer, downgrade writer to reader, and
48 /// have those locks automatically release when the ScopedLock is destroyed.
49 ///
51 {
52  static constexpr int OneReader = 2;
53  static constexpr int WriterFlag = 1;
54 
55 public:
56 
57  /// Construct a mutex, initially unlocked.
58  TfSpinRWMutex() : _lockState(0) {}
59 
60  /// Scoped lock utility class. API modeled roughly after
61  /// tbb::spin_rw_mutex::scoped_lock.
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 
69  /// Construct a scoped lock for mutex \p m and acquire either a read or
70  /// a write lock depending on \p write.
71  explicit ScopedLock(TfSpinRWMutex &m, bool write=true)
72  : _mutex(&m)
73  , _acqState(NotAcquired) {
74  Acquire(write);
75  }
76 
77  /// Construct a scoped lock not associated with a \p mutex.
78  ScopedLock() : _mutex(nullptr), _acqState(NotAcquired) {}
79 
80  /// If this scoped lock is acquired for either read or write, Release()
81  /// it.
83  Release();
84  }
85 
86  /// If the current scoped lock is acquired, Release() it, then associate
87  /// this lock with \p m and acquire either a read or a write lock,
88  /// depending on \p write.
89  void Acquire(TfSpinRWMutex &m, bool write=true) {
90  Release();
91  _mutex = &m;
92  Acquire(write);
93  }
94 
95  /// Acquire either a read or write lock on this lock's associated mutex
96  /// depending on \p write. This lock must be associated with a mutex
97  /// (typically by construction or by a call to Acquire() that takes a
98  /// mutex). This lock must not already be acquired when calling
99  /// Acquire().
100  void Acquire(bool write=true) {
101  if (write) {
102  AcquireWrite();
103  }
104  else {
105  AcquireRead();
106  }
107  }
108 
109  /// Release the currently required lock on the associated mutex. If
110  /// this lock is not currently acquired, silently do nothing.
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 
125  /// Acquire a read lock on this lock's associated mutex. This lock must
126  /// not already be acquired when calling \p AcquireRead().
127  void AcquireRead() {
128  TF_DEV_AXIOM(_acqState == NotAcquired);
129  _mutex->AcquireRead();
130  _acqState = ReadAcquired;
131  }
132 
133  /// Acquire a write lock on this lock's associated mutex. This lock
134  /// must not already be acquired when calling \p AcquireWrite().
135  void AcquireWrite() {
136  TF_DEV_AXIOM(_acqState == NotAcquired);
137  _mutex->AcquireWrite();
138  _acqState = WriteAcquired;
139  }
140 
141  /// Change this lock's acquisition state from a read lock to a write
142  /// lock. This lock must already be acquired for reading. Return true
143  /// if the upgrade occurred without releasing the read lock, false if it
144  /// was released.
146  TF_DEV_AXIOM(_acqState == ReadAcquired);
147  _acqState = WriteAcquired;
148  return _mutex->UpgradeToWriter();
149  }
150 
151  /// Change this lock's acquisition state from a write lock to a read
152  /// lock. This lock must already be acquired for writing. Return true
153  /// if the downgrade occurred without releasing the write in the
154  /// interim, false if it was released and other writers may have
155  /// intervened.
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 
180  /// Attempt to acquire a read lock on this mutex without waiting for
181  /// writers. This thread must not already hold a lock on this mutex (either
182  /// read or write). Return true if the lock is acquired, false otherwise.
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 
197  /// Acquire a read lock on this mutex. This thread must not already hold a
198  /// lock on this mutex (either read or write). Consider calling
199  /// DowngradeToReader() if this thread holds a write lock.
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 
211  /// Release this thread's read lock on this mutex.
212  inline void ReleaseRead() {
213  // Just decrement the count.
214  _lockState -= OneReader;
215  }
216 
217  /// Attempt to acquire a write lock on this mutex without waiting for other
218  /// writers. This thread must not already hold a lock on this mutex (either
219  /// read or write). Return true if the lock is acquired, false otherwise.
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 
233  /// Acquire a write lock on this mutex. This thread must not already hold a
234  /// lock on this mutex (either read or write). Consider calling
235  /// UpgradeToWriter() if this thread holds a read lock.
236  void AcquireWrite() {
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 
247  /// Release this thread's write lock on this mutex.
248  inline void ReleaseWrite() {
249  _lockState &= ~WriterFlag;
250  }
251 
252  /// Upgrade this thread's lock on this mutex (which must be a read lock) to
253  /// a write lock. Return true if the upgrade is done "atomically" meaning
254  /// that the read lock was not released (and thus no other writer could have
255  /// acquired the lock in the interim). Return false if this lock was
256  /// released and thus another writer could have taken the lock in the
257  /// interim.
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 
281  /// Downgrade this mutex, which must be locked for write by this thread, to
282  /// being locked for read by this thread. Return true if the downgrade
283  /// happened "atomically", meaning that the write lock was not released (and
284  /// thus possibly acquired by another thread). This implementation
285  /// currently always returns true.
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 
293 private:
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 
340 
341 #endif // PXR_BASE_TF_SPIN_RW_MUTEX_H
#define ARCH_LIKELY(x)
Definition: hints.h:29
ScopedLock()
Construct a scoped lock not associated with a mutex.
Definition: spinRWMutex.h:78
bool TryAcquireWrite()
Definition: spinRWMutex.h:220
#define TF_API
Definition: api.h:23
static constexpr int ReadAcquired
Definition: spinRWMutex.h:66
bool DowngradeToReader()
Definition: spinRWMutex.h:286
void Acquire(bool write=true)
Definition: spinRWMutex.h:100
#define TF_DEV_AXIOM(cond)
void AcquireWrite()
Definition: spinRWMutex.h:236
bool UpgradeToWriter()
Definition: spinRWMutex.h:258
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1425
void AcquireRead()
Definition: spinRWMutex.h:200
bool TryAcquireRead()
Definition: spinRWMutex.h:183
TfSpinRWMutex()
Construct a mutex, initially unlocked.
Definition: spinRWMutex.h:58
static constexpr int NotAcquired
Definition: spinRWMutex.h:65
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
static constexpr int WriteAcquired
Definition: spinRWMutex.h:67
void ReleaseWrite()
Release this thread's write lock on this mutex.
Definition: spinRWMutex.h:248
void ReleaseRead()
Release this thread's read lock on this mutex.
Definition: spinRWMutex.h:212
state
Definition: core.h:2289
ScopedLock(TfSpinRWMutex &m, bool write=true)
Definition: spinRWMutex.h:71
void Acquire(TfSpinRWMutex &m, bool write=true)
Definition: spinRWMutex.h:89