1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
7 // Central spin logic used across the entire code-base.
9 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
11 using System
.Diagnostics
;
13 namespace System
.Threading
15 // SpinWait is just a little value type that encapsulates some common spinning
16 // logic. It ensures we always yield on single-proc machines (instead of using busy
17 // waits), and that we work well on HT. It encapsulates a good mixture of spinning
18 // and real yielding. It's a value type so that various areas of the engine can use
19 // one by allocating it on the stack w/out unnecessary GC allocation overhead, e.g.:
22 // SpinWait wait = new SpinWait();
23 // while (!p) { wait.SpinOnce(); }
27 // Internally it just maintains a counter that is used to decide when to yield, etc.
29 // A common usage is to spin before blocking. In those cases, the NextSpinWillYield
30 // property allows a user to decide to fall back to waiting once it returns true:
33 // SpinWait wait = new SpinWait();
35 // if (wait.NextSpinWillYield) { /* block! */ }
36 // else { wait.SpinOnce(); }
42 /// Provides support for spin-based waiting.
46 /// <see cref="SpinWait"/> encapsulates common spinning logic. On single-processor machines, yields are
47 /// always used instead of busy waits, and on computers with Intel(R) processors employing Hyper-Threading
48 /// technology, it helps to prevent hardware thread starvation. SpinWait encapsulates a good mixture of
49 /// spinning and true yielding.
52 /// <see cref="SpinWait"/> is a value type, which means that low-level code can utilize SpinWait without
53 /// fear of unnecessary allocation overheads. SpinWait is not generally useful for ordinary applications.
54 /// In most cases, you should use the synchronization classes provided by the .NET Framework, such as
55 /// <see cref="System.Threading.Monitor"/>. For most purposes where spin waiting is required, however,
56 /// the <see cref="SpinWait"/> type should be preferred over the <see
57 /// cref="System.Threading.Thread.SpinWait"/> method.
60 /// While SpinWait is designed to be used in concurrent applications, it is not designed to be
61 /// used from multiple threads concurrently. SpinWait's members are not thread-safe. If multiple
62 /// threads must spin, each should use its own instance of SpinWait.
65 public struct SpinWait
67 // These constants determine the frequency of yields versus spinning. The
68 // numbers may seem fairly arbitrary, but were derived with at least some
69 // thought in the design document. I fully expect they will need to change
70 // over time as we gain more experience with performance.
71 internal const int YieldThreshold
= 10; // When to switch over to a true yield.
72 private const int Sleep0EveryHowManyYields
= 5; // After how many yields should we Sleep(0)?
73 internal const int DefaultSleep1Threshold
= 20; // After how many yields should we Sleep(1) frequently?
76 /// A suggested number of spin iterations before doing a proper wait, such as waiting on an event that becomes signaled
77 /// when the resource becomes available.
80 /// These numbers were arrived at by experimenting with different numbers in various cases that currently use it. It's
81 /// only a suggested value and typically works well when the proper wait is something like an event.
83 /// Spinning less can lead to early waiting and more context switching, spinning more can decrease latency but may use
84 /// up some CPU time unnecessarily. Depends on the situation too, for instance SemaphoreSlim uses more iterations
85 /// because the waiting there is currently a lot more expensive (involves more spinning, taking a lock, etc.). It also
86 /// depends on the likelihood of the spin being successful and how long the wait would be but those are not accounted
89 internal static readonly int SpinCountforSpinBeforeWait
= PlatformHelper
.IsSingleProcessor
? 1 : 35;
91 // The number of times we've spun already.
95 /// Gets the number of times <see cref="SpinOnce()"/> has been called on this instance.
102 Debug
.Assert(value >= 0);
108 /// Gets whether the next call to <see cref="SpinOnce()"/> will yield the processor, triggering a
109 /// forced context switch.
111 /// <value>Whether the next call to <see cref="SpinOnce()"/> will yield the processor, triggering a
112 /// forced context switch.</value>
114 /// On a single-CPU machine, <see cref="SpinOnce()"/> always yields the processor. On machines with
115 /// multiple CPUs, <see cref="SpinOnce()"/> may yield after an unspecified number of calls.
117 public bool NextSpinWillYield
=> _count
>= YieldThreshold
|| PlatformHelper
.IsSingleProcessor
;
120 /// Performs a single spin.
123 /// This is typically called in a loop, and may change in behavior based on the number of times a
124 /// <see cref="SpinOnce()"/> has been called thus far on this instance.
126 public void SpinOnce()
128 SpinOnceCore(DefaultSleep1Threshold
);
132 /// Performs a single spin.
134 /// <param name="sleep1Threshold">
135 /// A minimum spin count after which <code>Thread.Sleep(1)</code> may be used. A value of <code>-1</code> may be used to
136 /// disable the use of <code>Thread.Sleep(1)</code>.
138 /// <exception cref="ArgumentOutOfRangeException">
139 /// <paramref name="sleep1Threshold"/> is less than <code>-1</code>.
142 /// This is typically called in a loop, and may change in behavior based on the number of times a
143 /// <see cref="SpinOnce()"/> has been called thus far on this instance.
145 public void SpinOnce(int sleep1Threshold
)
147 if (sleep1Threshold
< -1)
149 throw new ArgumentOutOfRangeException(nameof(sleep1Threshold
), sleep1Threshold
, SR
.ArgumentOutOfRange_NeedNonNegOrNegative1
);
152 if (sleep1Threshold
>= 0 && sleep1Threshold
< YieldThreshold
)
154 sleep1Threshold
= YieldThreshold
;
157 SpinOnceCore(sleep1Threshold
);
160 private void SpinOnceCore(int sleep1Threshold
)
162 Debug
.Assert(sleep1Threshold
>= -1);
163 Debug
.Assert(sleep1Threshold
< 0 || sleep1Threshold
>= YieldThreshold
);
165 // (_count - YieldThreshold) % 2 == 0: The purpose of this check is to interleave Thread.Yield/Sleep(0) with
166 // Thread.SpinWait. Otherwise, the following issues occur:
167 // - When there are no threads to switch to, Yield and Sleep(0) become no-op and it turns the spin loop into a
168 // busy-spin that may quickly reach the max spin count and cause the thread to enter a wait state, or may
169 // just busy-spin for longer than desired before a Sleep(1). Completing the spin loop too early can cause
170 // excessive context switcing if a wait follows, and entering the Sleep(1) stage too early can cause
172 // - If there are multiple threads doing Yield and Sleep(0) (typically from the same spin loop due to
173 // contention), they may switch between one another, delaying work that can make progress.
175 _count
>= YieldThreshold
&&
176 ((_count
>= sleep1Threshold
&& sleep1Threshold
>= 0) || (_count
- YieldThreshold
) % 2 == 0)
178 PlatformHelper
.IsSingleProcessor
)
183 // We prefer to call Thread.Yield first, triggering a SwitchToThread. This
184 // unfortunately doesn't consider all runnable threads on all OS SKUs. In
185 // some cases, it may only consult the runnable threads whose ideal processor
186 // is the one currently executing code. Thus we occasionally issue a call to
187 // Sleep(0), which considers all runnable threads at equal priority. Even this
188 // is insufficient since we may be spin waiting for lower priority threads to
189 // execute; we therefore must call Sleep(1) once in a while too, which considers
190 // all runnable threads, regardless of ideal processor and priority, but may
191 // remove the thread from the scheduler's queue for 10+ms, if the system is
192 // configured to use the (default) coarse-grained system timer.
195 if (_count
>= sleep1Threshold
&& sleep1Threshold
>= 0)
201 int yieldsSoFar
= _count
>= YieldThreshold
? (_count
- YieldThreshold
) / 2 : _count
;
202 if ((yieldsSoFar
% Sleep0EveryHowManyYields
) == (Sleep0EveryHowManyYields
- 1))
215 // Otherwise, we will spin.
217 // We do this using the CLR's SpinWait API, which is just a busy loop that
218 // issues YIELD/PAUSE instructions to ensure multi-threaded CPUs can react
219 // intelligently to avoid starving. (These are NOOPs on other CPUs.) We
220 // choose a number for the loop iteration count such that each successive
221 // call spins for longer, to reduce cache contention. We cap the total
222 // number of spins we are willing to tolerate to reduce delay to the caller,
223 // since we expect most callers will eventually block anyway.
225 // Also, cap the maximum spin count to a value such that many thousands of CPU cycles would not be wasted doing
226 // the equivalent of YieldProcessor(), as at that point SwitchToThread/Sleep(0) are more likely to be able to
227 // allow other useful work to run. Long YieldProcessor() loops can help to reduce contention, but Sleep(1) is
228 // usually better for that.
230 // Thread.OptimalMaxSpinWaitsPerSpinIteration:
231 // - See Thread::InitializeYieldProcessorNormalized(), which describes and calculates this value.
233 int n
= Thread
.OptimalMaxSpinWaitsPerSpinIteration
;
234 if (_count
<= 30 && (1 << _count
) < n
)
241 // Finally, increment our spin counter.
242 _count
= (_count
== int.MaxValue
? YieldThreshold
: _count
+ 1);
246 /// Resets the spin counter.
249 /// This makes <see cref="SpinOnce()"/> and <see cref="NextSpinWillYield"/> behave as though no calls
250 /// to <see cref="SpinOnce()"/> had been issued on this instance. If a <see cref="SpinWait"/> instance
251 /// is reused many times, it may be useful to reset it to avoid yielding too soon.
258 #region Static Methods
260 /// Spins until the specified condition is satisfied.
262 /// <param name="condition">A delegate to be executed over and over until it returns true.</param>
263 /// <exception cref="ArgumentNullException">The <paramref name="condition"/> argument is null.</exception>
264 public static void SpinUntil(Func
<bool> condition
)
269 SpinUntil(condition
, Timeout
.Infinite
);
271 Debug
.Assert(result
);
276 /// Spins until the specified condition is satisfied or until the specified timeout is expired.
278 /// <param name="condition">A delegate to be executed over and over until it returns true.</param>
279 /// <param name="timeout">
280 /// A <see cref="TimeSpan"/> that represents the number of milliseconds to wait,
281 /// or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
282 /// <returns>True if the condition is satisfied within the timeout; otherwise, false</returns>
283 /// <exception cref="ArgumentNullException">The <paramref name="condition"/> argument is null.</exception>
284 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative number
285 /// other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than
286 /// <see cref="int.MaxValue"/>.</exception>
287 public static bool SpinUntil(Func
<bool> condition
, TimeSpan timeout
)
289 // Validate the timeout
290 long totalMilliseconds
= (long)timeout
.TotalMilliseconds
;
291 if (totalMilliseconds
< -1 || totalMilliseconds
> int.MaxValue
)
293 throw new System
.ArgumentOutOfRangeException(
294 nameof(timeout
), timeout
, SR
.SpinWait_SpinUntil_TimeoutWrong
);
297 // Call wait with the timeout milliseconds
298 return SpinUntil(condition
, (int)totalMilliseconds
);
302 /// Spins until the specified condition is satisfied or until the specified timeout is expired.
304 /// <param name="condition">A delegate to be executed over and over until it returns true.</param>
305 /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
306 /// cref="System.Threading.Timeout.Infinite"/> (-1) to wait indefinitely.</param>
307 /// <returns>True if the condition is satisfied within the timeout; otherwise, false</returns>
308 /// <exception cref="ArgumentNullException">The <paramref name="condition"/> argument is null.</exception>
309 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
310 /// negative number other than -1, which represents an infinite time-out.</exception>
311 public static bool SpinUntil(Func
<bool> condition
, int millisecondsTimeout
)
313 if (millisecondsTimeout
< Timeout
.Infinite
)
315 throw new ArgumentOutOfRangeException(
316 nameof(millisecondsTimeout
), millisecondsTimeout
, SR
.SpinWait_SpinUntil_TimeoutWrong
);
318 if (condition
== null)
320 throw new ArgumentNullException(nameof(condition
), SR
.SpinWait_SpinUntil_ArgumentNull
);
323 if (millisecondsTimeout
!= 0 && millisecondsTimeout
!= Timeout
.Infinite
)
325 startTime
= TimeoutHelper
.GetTime();
327 SpinWait spinner
= new SpinWait();
330 if (millisecondsTimeout
== 0)
337 if (millisecondsTimeout
!= Timeout
.Infinite
&& spinner
.NextSpinWillYield
)
339 if (millisecondsTimeout
<= (TimeoutHelper
.GetTime() - startTime
))
351 /// A helper class to get the number of processors, it updates the numbers of processors every sampling interval.
353 internal static class PlatformHelper
355 private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS
= 30000; // How often to refresh the count, in milliseconds.
356 private static volatile int s_processorCount
; // The last count seen.
357 private static volatile int s_lastProcessorCountRefreshTicks
; // The last time we refreshed.
360 /// Gets the number of available processors
362 internal static int ProcessorCount
366 int now
= Environment
.TickCount
;
367 int procCount
= s_processorCount
;
368 if (procCount
== 0 || (now
- s_lastProcessorCountRefreshTicks
) >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS
)
370 s_processorCount
= procCount
= Environment
.ProcessorCount
;
371 s_lastProcessorCountRefreshTicks
= now
;
374 Debug
.Assert(procCount
> 0,
375 "Processor count should be greater than 0.");
382 /// Gets whether the current machine has only a single processor.
384 /// <remarks>This typically does not change on a machine, so it's checked only once.</remarks>
385 internal static readonly bool IsSingleProcessor
= ProcessorCount
== 1;