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 
35 PXR_NAMESPACE_OPEN_SCOPE
36 
68 {
69  static constexpr int OneReader = 2;
70  static constexpr int WriterFlag = 1;
71 
72 public:
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 
152  void AcquireWrite() {
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 
253  void AcquireWrite() {
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 
310 private:
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 
356 PXR_NAMESPACE_CLOSE_SCOPE
357 
358 #endif // PXR_BASE_TF_SPIN_RW_MUTEX_H
#define TF_DEV_AXIOM(cond)
The same as TF_AXIOM, but compiled only in dev builds.
Definition: diagnostic.h:222
bool TryAcquireWrite()
Attempt to acquire a write lock on this mutex without waiting for other writers.
Definition: spinRWMutex.h:237
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 AcquireWrite()
Acquire a write lock on this lock's associated mutex.
Definition: spinRWMutex.h:152
~ScopedLock()
If this scoped lock is acquired for either read or write, Release() it.
Definition: spinRWMutex.h:99
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
This class implements a readers-writer mutex and provides a scoped lock utility.
Definition: bigRWMutex.h:69
Compiler hints.
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
bool DowngradeToReader()
Change this lock's acquisition state from a write lock to a read lock.
Definition: spinRWMutex.h:173
Scoped lock utility class.
Definition: spinRWMutex.h:79
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
void AcquireRead()
Acquire a read lock on this mutex.
Definition: spinRWMutex.h:217
ScopedLock()
Construct a scoped lock not associated with a mutex.
Definition: spinRWMutex.h:95
void AcquireRead()
Acquire a read lock on this lock's associated mutex.
Definition: spinRWMutex.h:144
void AcquireWrite()
Acquire a write lock on this mutex.
Definition: spinRWMutex.h:253
bool UpgradeToWriter()
Upgrade this thread's lock on this mutex (which must be a read lock) to a write lock.
Definition: spinRWMutex.h:275
void Release()
Release the currently required lock on the associated mutex.
Definition: spinRWMutex.h:128
This class implements a readers-writer spin lock that emphasizes throughput when there is light conte...
Definition: spinRWMutex.h:67
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
void ReleaseWrite()
Release this thread's write lock on this mutex.
Definition: spinRWMutex.h:265
Stripped down version of diagnostic.h that doesn't define std::string.