VLink  2.0.0
A high-performance communication middleware
spin_lock.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file spin_lock.h
26  * @brief Cache-line aligned adaptive spin lock with exponential back-off.
27  *
28  * @details
29  * @c vlink::SpinLock is a user-space mutex aimed at very short critical sections (a few
30  * instructions) where a @c std::mutex context switch would dominate the cost. It is used
31  * inside @c CpuProfiler and other hot-path internals.
32  *
33  * The acquisition loop applies exponential back-off so contention does not saturate the
34  * cache-coherence bus. Each back-off ladder rung doubles the inner spin budget until the
35  * cap is reached, then a CPU-pause instruction yields the core; once the total spin count
36  * exceeds the hard ceiling the back-off action switches to a sleep:
37  *
38  * @verbatim
39  * round 1 | XXXX---- 8 spins ----> yield_cpu()
40  * round 2 | XXXX-XXX 16 spins ----> yield_cpu()
41  * round 3 | XXXXXXXX 32 spins ----> yield_cpu()
42  * ... | ... up to 1024 spins per ladder rung
43  * total > 50000 spins ----> sleep_for(10 us) (latched warn-once)
44  * @endverbatim
45  *
46  * Comparison with @c std::mutex:
47  *
48  * | Property | @c SpinLock | @c std::mutex |
49  * | ------------------- | -------------------------------------- | ---------------------------- |
50  * | Wait strategy | Adaptive busy-spin + yield + sleep | OS futex / kernel wait |
51  * | Critical-section | Single-digit microseconds | Any duration |
52  * | Recursive | No (deadlocks on re-entry) | Use @c std::recursive_mutex |
53  * | False sharing | Avoided via @c alignas(64) | Implementation defined |
54  * | Header-only | Yes (no @c VLINK_EXPORT) | Library symbol |
55  *
56  * @note
57  * - The lock is non-recursive; double-locking from the same thread spins forever.
58  * - The hard-ceiling warning is latched per instance so a permanently contended lock does
59  * not flood the log.
60  *
61  * @par Example
62  * @code
63  * vlink::SpinLock lock;
64  *
65  * lock.lock();
66  * critical_section();
67  * lock.unlock();
68  *
69  * {
70  * vlink::SpinLockGuard guard(lock);
71  * critical_section();
72  * }
73  * @endcode
74  */
75 
76 #pragma once
77 
78 #include <atomic>
79 #include <chrono>
80 #include <cstdint>
81 #include <thread>
82 
83 #include "./logger.h"
84 #include "./macros.h"
85 #include "./utils.h"
86 
87 #ifdef _MSC_VER
88 extern "C" void _mm_pause(void);
89 #endif
90 
91 namespace vlink {
92 
93 /**
94  * @class SpinLock
95  * @brief Adaptive spin lock satisfying the C++ @c Lockable named requirement.
96  *
97  * @details
98  * Cache-line aligned through @c alignas(64) to prevent false sharing. Composes with
99  * @c std::lock_guard, @c std::unique_lock and the supplied @c SpinLockGuard.
100  */
101 class SpinLock final {
102  public:
103  /**
104  * @brief Constructs an unlocked spin lock.
105  */
106  SpinLock() noexcept = default;
107 
108  /**
109  * @brief Destructor. Assumes the lock has already been released.
110  */
111  ~SpinLock() noexcept = default;
112 
113  /**
114  * @brief Acquires the lock, spinning with adaptive back-off until it becomes free.
115  *
116  * @details
117  * The inner loop alternates @c exchange attempts with relaxed-load spins. Each back-off
118  * rung doubles the spin budget from @c 8 up to @c 1024 then yields the CPU. After
119  * @c 50000 spins the back-off action switches to a 10 us sleep and a one-time warning
120  * is emitted.
121  *
122  * @warning Recursive acquisition by the same thread deadlocks the lock.
123  */
124  void lock() noexcept;
125 
126  /**
127  * @brief Attempts a single non-blocking acquire of the lock.
128  *
129  * @return @c true when the lock was successfully acquired.
130  */
131  bool try_lock() noexcept;
132 
133  /**
134  * @brief Releases the lock with @c memory_order_release.
135  *
136  * @details
137  * May only be called by the thread that currently owns the lock.
138  */
139  void unlock() noexcept;
140 
141  private:
142  static constexpr size_t kInterferenceSize = 64U;
143 
144  alignas(kInterferenceSize) std::atomic<bool> flag_{false};
145  std::atomic<bool> warned_{false};
146 
148 };
149 
150 /**
151  * @class SpinLockGuard
152  * @brief RAII wrapper that locks a @c SpinLock on construction and unlocks on destruction.
153  *
154  * @details
155  * Equivalent to @c std::lock_guard<SpinLock>. Preferred over manual @c lock / @c unlock
156  * because it is exception-safe.
157  *
158  * @par Example
159  * @code
160  * SpinLock my_lock;
161  * {
162  * SpinLockGuard guard(my_lock);
163  * critical_section();
164  * }
165  * @endcode
166  */
167 class SpinLockGuard final {
168  public:
169  /**
170  * @brief Acquires @p lock immediately.
171  *
172  * @param lock Spin lock to acquire. Must outlive this guard.
173  */
174  explicit SpinLockGuard(SpinLock& lock) noexcept;
175 
176  /**
177  * @brief Releases the held spin lock.
178  */
179  ~SpinLockGuard() noexcept;
180 
181  private:
182  SpinLock& lock_;
183 
185 };
186 
187 ////////////////////////////////////////////////////////////////
188 /// Details
189 ////////////////////////////////////////////////////////////////
190 
191 inline void SpinLock::lock() noexcept {
192  constexpr static uint32_t kMaxSpinCount = 50000U;
193 
194  constexpr static uint16_t kInitialBackoff = 8U;
195  constexpr static uint16_t kMaxBackoff = 1024U;
196 
197  uint32_t total_spin = 0;
198  uint16_t spin_count = 0;
199  uint16_t backoff = kInitialBackoff;
200 
201  for (;;) {
202  if (!flag_.exchange(true, std::memory_order_acquire)) {
203  return;
204  }
205 
206  do {
207  ++total_spin;
208 
209  if (++spin_count >= backoff) {
210  if VUNLIKELY (total_spin > kMaxSpinCount) {
211  if (!warned_.exchange(true, std::memory_order_relaxed)) {
212  VLOG_E("SpinLock: exceeded max spin count.");
213  }
214  std::this_thread::sleep_for(std::chrono::microseconds(10));
215  } else {
217  }
218 
219  if (backoff < kMaxBackoff) {
220  backoff = static_cast<uint16_t>(backoff * 2U);
221  }
222 
223  spin_count = 0;
224  }
225  } while (flag_.load(std::memory_order_relaxed));
226  }
227 }
228 
229 inline bool SpinLock::try_lock() noexcept {
230  if VUNLIKELY (flag_.load(std::memory_order_relaxed)) {
231  return false;
232  }
233 
234  return !flag_.exchange(true, std::memory_order_acquire);
235 }
236 
237 inline void SpinLock::unlock() noexcept { flag_.store(false, std::memory_order_release); }
238 
239 inline SpinLockGuard::SpinLockGuard(SpinLock& lock) noexcept : lock_(lock) { lock_.lock(); }
240 
241 inline SpinLockGuard::~SpinLockGuard() noexcept { lock_.unlock(); }
242 
243 } // namespace vlink
Singleton logger with stream / format / printf / RAII-stream entry points.
#define VLOG_E(...)
Definition: logger.h:789
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174
Portable host-system utility surface used across the VLink runtime.