Fix StyleCop warning SA1121 (use built-in types)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Threading / SpinWait.cs
blob56ee7c4df7fc38f773cd65609183fbd564bca252
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 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6 //
7 // Central spin logic used across the entire code-base.
8 //
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.:
21 // void f() {
22 // SpinWait wait = new SpinWait();
23 // while (!p) { wait.SpinOnce(); }
24 // ...
25 // }
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:
32 // void f() {
33 // SpinWait wait = new SpinWait();
34 // while (!p) {
35 // if (wait.NextSpinWillYield) { /* block! */ }
36 // else { wait.SpinOnce(); }
37 // }
38 // ...
39 // }
41 /// <summary>
42 /// Provides support for spin-based waiting.
43 /// </summary>
44 /// <remarks>
45 /// <para>
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.
50 /// </para>
51 /// <para>
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.
58 /// </para>
59 /// <para>
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.
63 /// </para>
64 /// </remarks>
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?
75 /// <summary>
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.
78 /// </summary>
79 /// <remarks>
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.
82 ///
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
87 /// for here.
88 /// </remarks>
89 internal static readonly int SpinCountforSpinBeforeWait = PlatformHelper.IsSingleProcessor ? 1 : 35;
91 // The number of times we've spun already.
92 private int _count;
94 /// <summary>
95 /// Gets the number of times <see cref="SpinOnce()"/> has been called on this instance.
96 /// </summary>
97 public int Count
99 get => _count;
100 internal set
102 Debug.Assert(value >= 0);
103 _count = value;
107 /// <summary>
108 /// Gets whether the next call to <see cref="SpinOnce()"/> will yield the processor, triggering a
109 /// forced context switch.
110 /// </summary>
111 /// <value>Whether the next call to <see cref="SpinOnce()"/> will yield the processor, triggering a
112 /// forced context switch.</value>
113 /// <remarks>
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.
116 /// </remarks>
117 public bool NextSpinWillYield => _count >= YieldThreshold || PlatformHelper.IsSingleProcessor;
119 /// <summary>
120 /// Performs a single spin.
121 /// </summary>
122 /// <remarks>
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.
125 /// </remarks>
126 public void SpinOnce()
128 SpinOnceCore(DefaultSleep1Threshold);
131 /// <summary>
132 /// Performs a single spin.
133 /// </summary>
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>.
137 /// </param>
138 /// <exception cref="ArgumentOutOfRangeException">
139 /// <paramref name="sleep1Threshold"/> is less than <code>-1</code>.
140 /// </exception>
141 /// <remarks>
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.
144 /// </remarks>
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
171 // excessive delays.
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.
174 if ((
175 _count >= YieldThreshold &&
176 ((_count >= sleep1Threshold && sleep1Threshold >= 0) || (_count - YieldThreshold) % 2 == 0)
177 ) ||
178 PlatformHelper.IsSingleProcessor)
181 // We must yield.
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)
197 Thread.Sleep(1);
199 else
201 int yieldsSoFar = _count >= YieldThreshold ? (_count - YieldThreshold) / 2 : _count;
202 if ((yieldsSoFar % Sleep0EveryHowManyYields) == (Sleep0EveryHowManyYields - 1))
204 Thread.Sleep(0);
206 else
208 Thread.Yield();
212 else
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)
236 n = 1 << _count;
238 Thread.SpinWait(n);
241 // Finally, increment our spin counter.
242 _count = (_count == int.MaxValue ? YieldThreshold : _count + 1);
245 /// <summary>
246 /// Resets the spin counter.
247 /// </summary>
248 /// <remarks>
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.
252 /// </remarks>
253 public void Reset()
255 _count = 0;
258 #region Static Methods
259 /// <summary>
260 /// Spins until the specified condition is satisfied.
261 /// </summary>
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)
266 #if DEBUG
267 bool result =
268 #endif
269 SpinUntil(condition, Timeout.Infinite);
270 #if DEBUG
271 Debug.Assert(result);
272 #endif
275 /// <summary>
276 /// Spins until the specified condition is satisfied or until the specified timeout is expired.
277 /// </summary>
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);
301 /// <summary>
302 /// Spins until the specified condition is satisfied or until the specified timeout is expired.
303 /// </summary>
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);
322 uint startTime = 0;
323 if (millisecondsTimeout != 0 && millisecondsTimeout != Timeout.Infinite)
325 startTime = TimeoutHelper.GetTime();
327 SpinWait spinner = new SpinWait();
328 while (!condition())
330 if (millisecondsTimeout == 0)
332 return false;
335 spinner.SpinOnce();
337 if (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield)
339 if (millisecondsTimeout <= (TimeoutHelper.GetTime() - startTime))
341 return false;
345 return true;
347 #endregion
350 /// <summary>
351 /// A helper class to get the number of processors, it updates the numbers of processors every sampling interval.
352 /// </summary>
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.
359 /// <summary>
360 /// Gets the number of available processors
361 /// </summary>
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.");
377 return procCount;
381 /// <summary>
382 /// Gets whether the current machine has only a single processor.
383 /// </summary>
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;