HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
spinMutex.h
Go to the documentation of this file.
1 //
2 // Copyright 2023 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_MUTEX_H
8 #define PXR_BASE_TF_SPIN_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 
20 
21 /// \class TfSpinMutex
22 ///
23 /// This class implements a simple spin lock that emphasizes throughput when
24 /// there is little to no contention. Like all spin locks, any contention
25 /// performs poorly; consider a different algorithm design or synchronization
26 /// strategy in that case.
27 ///
28 /// This class provides a nested TfSpinMutex::ScopedLock that makes it easy to
29 /// acquire locks and have those locks automatically release when the ScopedLock
30 /// is destroyed.
31 ///
32 /// TfSpinMutex is observed to compile to the same instruction sequence as
33 /// tbb::spin_mutex on x86-64 for uncontended lock/unlock. The main difference
34 /// between TfSpinMutex and tbb:spin_mutex is that, for contended lock
35 /// operations, TfSpinMutex calls an out-of-line function to handle spinning &
36 /// backoff, while the tbb::spin_mutex inlines that code. This translates to 4
37 /// instructions inlined to take a TfSpinMutex lock, compared to 28 instructions
38 /// inlined for tbb:spin_mutex at the time of this writing. Correspondingly
39 /// tbb::spin_mutex offers ~2% better throughput under high contention. But
40 /// again, avoid spin locks if you have contention.
41 ///
43 {
44 public:
45 
46  /// Construct a mutex, initially unlocked.
47  TfSpinMutex() : _lockState(false) {}
48 
49  /// Tag type for constructing a ScopedLock associated with a mutex but not
50  /// yet acquired. Use with TfSpinMutex::deferAcquire.
51  struct DeferAcquire {};
52 
53  /// Tag value for deferred-acquisition ScopedLock construction.
54  static constexpr DeferAcquire deferAcquire {};
55 
56  /// Scoped lock utility class. API modeled roughly after
57  /// tbb::spin_rw_mutex::scoped_lock.
58  struct ScopedLock {
59 
60  /// Construct a scoped lock for mutex \p m and acquire a lock.
61  explicit ScopedLock(TfSpinMutex &m) : _mutex(&m) {
62  Acquire();
63  }
64 
65  /// Construct a scoped lock associated with mutex \p m but not yet
66  /// acquired. Use Acquire() or TryAcquire() to acquire the lock.
68 
69  /// Construct a scoped lock not associated with a \p mutex.
70  ScopedLock() = default;
71 
72  /// Construct a new lock taking the \p other lock's mutex association
73  /// and acquisition state. Leave \p other not associated with a mutex.
74  ScopedLock(ScopedLock &&other) noexcept
75  : _mutex(std::exchange(other._mutex, nullptr))
76  , _acquired(std::exchange(other._acquired, false)) {}
77 
78  /// If \p this is not the same object as \p other, Release(), take the
79  /// \p other lock's mutex association and acquisition state, and leave
80  /// \p other not associated with a mutex. If \p this is the same object
81  /// as \p other, do nothing. In either case, return \p *this.
82  ScopedLock &operator=(ScopedLock &&other) noexcept {
83  if (this != &other) {
84  Release();
85  _mutex = std::exchange(other._mutex, nullptr);
86  _acquired = std::exchange(other._acquired, false);
87  }
88  return *this;
89  }
90 
91  /// If this scoped lock is acquired, Release() it.
93  Release();
94  }
95 
96  /// If the current scoped lock is acquired, Release() it, then associate
97  /// this lock with \p m and acquire a lock.
98  void Acquire(TfSpinMutex &m) {
99  Release();
100  _mutex = &m;
101  Acquire();
102  }
103 
104  /// Release the currently required lock on the associated mutex. If
105  /// this lock is not currently acquired, do nothing.
106  void Release() {
107  if (_acquired) {
108  _Release();
109  }
110  }
111 
112  /// Acquire a lock on this lock's associated mutex. This lock must not
113  /// already be acquired when calling \p Acquire().
114  void Acquire() {
115  TF_DEV_AXIOM(_mutex);
116  TF_DEV_AXIOM(!_acquired);
117  _mutex->Acquire();
118  _acquired = true;
119  }
120 
121  /// If the current scoped lock is acquired, Release() it, then associate
122  /// this lock with \p m and try to acquire a lock. Return true if the
123  /// lock was successfully acquired, false if not.
125  Release();
126  _mutex = &m;
127  return TryAcquire();
128  }
129 
130  /// Try to acquire a lock on this lock's associated mutex. The lock
131  /// must not already be acquired when calling \p TryAcquire(). Return
132  /// true if the lock was successfully acquired, false if not.
133  bool TryAcquire() {
134  TF_DEV_AXIOM(_mutex);
135  TF_DEV_AXIOM(!_acquired);
136  _acquired = _mutex->TryAcquire();
137  return _acquired;
138  }
139 
140  private:
141 
142  void _Release() {
143  TF_DEV_AXIOM(_acquired);
144  _mutex->Release();
145  _acquired = false;
146  }
147 
148  TfSpinMutex *_mutex = nullptr;
149  bool _acquired = false;
150  };
151 
152  /// Acquire a lock on this mutex if it is not currently held by another
153  /// thread. Return true if the lock was acquired, or false if it was not
154  /// because another thread held the lock. This thread must not already hold
155  /// a lock on this mutex.
156  inline bool TryAcquire() {
157  return _lockState.exchange(true, std::memory_order_acquire) == false;
158  }
159 
160  /// Acquire a lock on this mutex. If another thread holds a lock on this
161  /// mutex, wait until it is released and this thread successfully acquires
162  /// it. This thread must not already hold a lock on this mutex.
163  void Acquire() {
164  if (ARCH_LIKELY(TryAcquire())) {
165  return;
166  }
167  _AcquireContended();
168  }
169 
170  /// Release this thread's lock on this mutex.
171  inline void Release() {
172  _lockState.store(false, std::memory_order_release);
173  }
174 
175 private:
176 
177  TF_API void _AcquireContended();
178 
179  std::atomic<bool> _lockState;
180 };
181 
183 
184 #endif // PXR_BASE_TF_SPIN_MUTEX_H
#define ARCH_LIKELY(x)
Definition: hints.h:29
void Release()
Release this thread's lock on this mutex.
Definition: spinMutex.h:171
static constexpr DeferAcquire deferAcquire
Tag value for deferred-acquisition ScopedLock construction.
Definition: spinMutex.h:54
#define TF_API
Definition: api.h:23
#define PXR_NAMESPACE_OPEN_SCOPE
Definition: pxr.h:73
~ScopedLock()
If this scoped lock is acquired, Release() it.
Definition: spinMutex.h:92
#define TF_DEV_AXIOM(cond)
ScopedLock(ScopedLock &&other) noexcept
Definition: spinMutex.h:74
ScopedLock()=default
Construct a scoped lock not associated with a mutex.
void Acquire(TfSpinMutex &m)
Definition: spinMutex.h:98
bool TryAcquire()
Definition: spinMutex.h:156
void Acquire()
Definition: spinMutex.h:163
ScopedLock(TfSpinMutex &m, TfSpinMutex::DeferAcquire)
Definition: spinMutex.h:67
VULKAN_HPP_CONSTEXPR_14 VULKAN_HPP_INLINE T exchange(T &obj, U &&newValue)
Definition: vulkan_raii.hpp:25
bool TryAcquire(TfSpinMutex &m)
Definition: spinMutex.h:124
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
ScopedLock & operator=(ScopedLock &&other) noexcept
Definition: spinMutex.h:82
TfSpinMutex()
Construct a mutex, initially unlocked.
Definition: spinMutex.h:47
ScopedLock(TfSpinMutex &m)
Construct a scoped lock for mutex m and acquire a lock.
Definition: spinMutex.h:61