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 
19 
20 /// \class TfSpinMutex
21 ///
22 /// This class implements a simple spin lock that emphasizes throughput when
23 /// there is little to no contention. Like all spin locks, any contention
24 /// performs poorly; consider a different algorithm design or synchronization
25 /// strategy in that case.
26 ///
27 /// This class provides a nested TfSpinMutex::ScopedLock that makes it easy to
28 /// acquire locks and have those locks automatically release when the ScopedLock
29 /// is destroyed.
30 ///
31 /// TfSpinMutex is observed to compile to the same instruction sequence as
32 /// tbb::spin_mutex on x86-64 for uncontended lock/unlock. The main difference
33 /// between TfSpinMutex and tbb:spin_mutex is that, for contended lock
34 /// operations, TfSpinMutex calls an out-of-line function to handle spinning &
35 /// backoff, while the tbb::spin_mutex inlines that code. This translates to 4
36 /// instructions inlined to take a TfSpinMutex lock, compared to 28 instructions
37 /// inlined for tbb:spin_mutex at the time of this writing. Correspondingly
38 /// tbb::spin_mutex offers ~2% better throughput under high contention. But
39 /// again, avoid spin locks if you have contention.
40 ///
42 {
43 public:
44 
45  /// Construct a mutex, initially unlocked.
46  TfSpinMutex() : _lockState(false) {}
47 
48  /// Scoped lock utility class. API modeled roughly after
49  /// tbb::spin_rw_mutex::scoped_lock.
50  struct ScopedLock {
51 
52  /// Construct a scoped lock for mutex \p m and acquire a lock.
53  explicit ScopedLock(TfSpinMutex &m)
54  : _mutex(&m)
55  , _acquired(false) {
56  Acquire();
57  }
58 
59  /// Construct a scoped lock not associated with a \p mutex.
60  ScopedLock() : _mutex(nullptr), _acquired(false) {}
61 
62  /// If this scoped lock is acquired, Release() it.
64  Release();
65  }
66 
67  /// If the current scoped lock is acquired, Release() it, then associate
68  /// this lock with \p m and acquire a lock.
69  void Acquire(TfSpinMutex &m) {
70  Release();
71  _mutex = &m;
72  Acquire();
73  }
74 
75  /// Release the currently required lock on the associated mutex. If
76  /// this lock is not currently acquired, silently do nothing.
77  void Release() {
78  if (_acquired) {
79  _Release();
80  }
81  }
82 
83  /// Acquire a lock on this lock's associated mutex. This lock must not
84  /// already be acquired when calling \p Acquire().
85  void Acquire() {
86  TF_DEV_AXIOM(!_acquired);
87  _mutex->Acquire();
88  _acquired = true;
89  }
90 
91  private:
92 
93  void _Release() {
94  TF_DEV_AXIOM(_acquired);
95  _mutex->Release();
96  _acquired = false;
97  }
98 
99  TfSpinMutex *_mutex;
100  bool _acquired;
101  };
102 
103  /// Acquire a lock on this mutex if it is not currently held by another
104  /// thread. Return true if the lock was acquired, or false if it was not
105  /// because another thread held the lock. This thread must not already hold
106  /// a lock on this mutex.
107  inline bool TryAcquire() {
108  return _lockState.exchange(true, std::memory_order_acquire) == false;
109  }
110 
111  /// Acquire a lock on this mutex. If another thread holds a lock on this
112  /// mutex, wait until it is released and this thread successfully acquires
113  /// it. This thread must not already hold a lock on this mutex.
114  void Acquire() {
115  if (ARCH_LIKELY(TryAcquire())) {
116  return;
117  }
118  _AcquireContended();
119  }
120 
121  /// Release this thread's lock on this mutex.
122  inline void Release() {
123  _lockState.store(false, std::memory_order_release);
124  }
125 
126 private:
127 
128  TF_API void _AcquireContended();
129 
130  std::atomic<bool> _lockState;
131 };
132 
134 
135 #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:122
#define TF_API
Definition: api.h:23
ScopedLock()
Construct a scoped lock not associated with a mutex.
Definition: spinMutex.h:60
~ScopedLock()
If this scoped lock is acquired, Release() it.
Definition: spinMutex.h:63
#define TF_DEV_AXIOM(cond)
void Acquire(TfSpinMutex &m)
Definition: spinMutex.h:69
bool TryAcquire()
Definition: spinMutex.h:107
void Acquire()
Definition: spinMutex.h:114
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1425
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
TfSpinMutex()
Construct a mutex, initially unlocked.
Definition: spinMutex.h:46
ScopedLock(TfSpinMutex &m)
Construct a scoped lock for mutex m and acquire a lock.
Definition: spinMutex.h:53