HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
bigRWMutex.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_BIG_RW_MUTEX_H
8 #define PXR_BASE_TF_BIG_RW_MUTEX_H
9 
10 #include "pxr/pxr.h"
11 #include "pxr/base/tf/api.h"
12 
13 #include "pxr/base/arch/align.h"
14 #include "pxr/base/arch/hints.h"
16 #include "pxr/base/tf/hash.h"
18 
19 #include <atomic>
20 #include <memory>
21 #include <thread>
22 
24 
25 /// \class TfBigRWMutex
26 ///
27 /// This class implements a readers-writer mutex and provides a scoped lock
28 /// utility. Multiple clients may acquire a read lock simultaneously, but only
29 /// one client may hold a write lock, exclusive to all other locks.
30 ///
31 /// This class emphasizes throughput for (and is thus best used in) the case
32 /// where there are many simultaneous reader clients all concurrently taking
33 /// read locks, with clients almost never taking write locks. As such, taking a
34 /// read lock is a lightweight operation that usually does not imply much
35 /// hardware-level concurrency penalty (i.e. writes to shared cache lines).
36 /// This is done by allocating several cache-line-sized chunks of memory to
37 /// represent lock state, and readers typically only deal with a single lock
38 /// state (and therefore a single cache line). On the other hand, taking a
39 /// write lock is very expensive from a hardware concurrency point of view; it
40 /// requires atomic memory operations on every cache-line.
41 ///
42 /// To achieve good throughput under highly read-contended workloads, this class
43 /// allocates 10s of cachelines worth of state (~1 KB) to help minimize
44 /// hardware-level contention. So it is probably not appropriate to use as
45 /// (e.g.) a member variable in an object that there are likely to be many of.
46 ///
47 /// This class has been measured to show >10x throughput compared to
48 /// tbb::spin_rw_mutex, and >100x better throughput compared to
49 /// tbb::queuing_rw_mutex on reader-contention-heavy loads. The tradeoff being
50 /// the relatively large size required compared to these other classes.
51 ///
53 {
54 public:
55  // Number of different cache-line-sized lock states.
56  static constexpr unsigned NumStates = 16;
57 
58  // Lock states -- 0 means not locked, -1 means locked for write, other
59  // positive values count the number of readers locking this particular lock
60  // state object.
61  static constexpr int NotLocked = 0;
62  static constexpr int WriteLocked = -1;
63 
64  /// Construct a mutex, initially unlocked.
66 
67  /// Scoped lock utility class. API modeled after
68  /// tbb::spin_rw_mutex::scoped_lock.
69  struct ScopedLock {
70 
71  // Acquisition states: -1 means not acquired, -2 means acquired for
72  // write (exclusive lock), >= 0 indicates locked for read, and the value
73  // indicates which lock state index the reader has incremented.
74  static constexpr int NotAcquired = -1;
75  static constexpr int WriteAcquired = -2;
76 
77  /// Construct a scoped lock for mutex \p m and acquire either a read or
78  /// a write lock depending on \p write.
79  explicit ScopedLock(TfBigRWMutex &m, bool write=true)
80  : _mutex(&m)
81  , _acqState(NotAcquired) {
82  Acquire(write);
83  }
84 
85  /// Construct a scoped lock not associated with a \p mutex.
86  ScopedLock() : _mutex(nullptr), _acqState(NotAcquired) {}
87 
88  /// If this scoped lock is acquired for either read or write, Release()
89  /// it.
91  Release();
92  }
93 
94  /// If the current scoped lock is acquired, Release() it, then associate
95  /// this lock with \p m and acquire either a read or a write lock,
96  /// depending on \p write.
97  void Acquire(TfBigRWMutex &m, bool write=true) {
98  Release();
99  _mutex = &m;
100  Acquire(write);
101  }
102 
103  /// Acquire either a read or write lock on this lock's associated mutex
104  /// depending on \p write. This lock must be associated with a mutex
105  /// (typically by construction or by a call to Acquire() that takes a
106  /// mutex). This lock must not already be acquired when calling
107  /// Acquire().
108  void Acquire(bool write=true) {
109  if (write) {
110  AcquireWrite();
111  }
112  else {
113  AcquireRead();
114  }
115  }
116 
117  /// Release the currently required lock on the associated mutex. If
118  /// this lock is not currently acquired, silently do nothing.
119  void Release() {
120  switch (_acqState) {
121  case NotAcquired:
122  break;
123  case WriteAcquired:
124  _ReleaseWrite();
125  break;
126  default:
127  _ReleaseRead();
128  break;
129  };
130  }
131 
132  /// Acquire a read lock on this lock's associated mutex. This lock must
133  /// not already be acquired when calling \p AcquireRead().
134  void AcquireRead() {
135  TF_AXIOM(_acqState == NotAcquired);
136  _acqState = _mutex->_AcquireRead(_GetSeed());
137  }
138 
139  /// Acquire a write lock on this lock's associated mutex. This lock
140  /// must not already be acquired when calling \p AcquireWrite().
141  void AcquireWrite() {
142  TF_AXIOM(_acqState == NotAcquired);
143  _mutex->_AcquireWrite();
144  _acqState = WriteAcquired;
145  }
146 
147  /// Change this lock's acquisition state from a read lock to a write
148  /// lock. This lock must already be acquired for reading. For
149  /// consistency with tbb, this function returns a bool indicating
150  /// whether the upgrade was done atomically, without releasing the
151  /// read-lock. However the current implementation always releases the
152  /// read lock so this function always returns false.
154  TF_AXIOM(_acqState >= 0);
155  Release();
156  AcquireWrite();
157  return false;
158  }
159 
160  private:
161 
162  void _ReleaseRead() {
163  TF_AXIOM(_acqState >= 0);
164  _mutex->_ReleaseRead(_acqState);
165  _acqState = NotAcquired;
166  }
167 
168  void _ReleaseWrite() {
169  TF_AXIOM(_acqState == WriteAcquired);
170  _mutex->_ReleaseWrite();
171  _acqState = NotAcquired;
172  }
173 
174  // Helper for returning a seed value associated with this lock object.
175  // This helps determine which lock state a read-lock should use.
176  inline int _GetSeed() const {
177  return static_cast<int>(
178  static_cast<unsigned>(TfHash()(this)) >> 8);
179  }
180 
181  TfBigRWMutex *_mutex;
182  int _acqState; // NotAcquired (-1), WriteAcquired (-2), otherwise
183  // acquired for read, and index indicates which lock
184  // state we are associated with.
185  };
186 
187 private:
188 
189  // Optimistic read-lock case inlined.
190  inline int _AcquireRead(int seed) {
191  // Determine a lock state index to use.
192  int stateIndex = seed % NumStates;
193  if (ARCH_UNLIKELY(_writerActive) ||
194  !_states[stateIndex].mutex.TryAcquireRead()) {
195  _AcquireReadContended(stateIndex);
196  }
197  return stateIndex;
198  }
199 
200  // Contended read-lock helper.
201  TF_API void _AcquireReadContended(int stateIndex);
202 
203  void _ReleaseRead(int stateIndex) {
204  _states[stateIndex].mutex.ReleaseRead();
205  }
206 
207  TF_API void _AcquireWrite();
208  TF_API void _ReleaseWrite();
209 
210  struct _LockState {
211  TfSpinRWMutex mutex;
212  // This padding ensures that \p state instances sit on different cache
213  // lines.
214  char _unused_padding[
215  ARCH_CACHE_LINE_SIZE-(sizeof(mutex) % ARCH_CACHE_LINE_SIZE)];
216  };
217 
218  std::unique_ptr<_LockState []> _states;
219  std::atomic<bool> _writerActive;
220 
221 };
222 
224 
225 #endif // PXR_BASE_TF_BIG_RW_MUTEX_H
static constexpr unsigned NumStates
Definition: bigRWMutex.h:56
#define TF_API
Definition: api.h:23
TF_API TfBigRWMutex()
Construct a mutex, initially unlocked.
void Acquire(TfBigRWMutex &m, bool write=true)
Definition: bigRWMutex.h:97
Definition: hash.h:472
#define ARCH_UNLIKELY(x)
Definition: hints.h:30
static constexpr int NotAcquired
Definition: bigRWMutex.h:74
static constexpr int NotLocked
Definition: bigRWMutex.h:61
ScopedLock(TfBigRWMutex &m, bool write=true)
Definition: bigRWMutex.h:79
static constexpr int WriteLocked
Definition: bigRWMutex.h:62
#define ARCH_CACHE_LINE_SIZE
Definition: align.h:67
#define TF_AXIOM(cond)
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1425
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
void Acquire(bool write=true)
Definition: bigRWMutex.h:108
ScopedLock()
Construct a scoped lock not associated with a mutex.
Definition: bigRWMutex.h:86
static constexpr int WriteAcquired
Definition: bigRWMutex.h:75