private void EnterMyLockSpin() { int pc = Environment.ProcessorCount; for (int i = 0; ; i++) { if (i < LockSpinCount && pc > 1) { RuntimeThread.SpinWait(LockSpinCycles * (i + 1)); // Wait a few dozen instructions to let another processor release lock. } else if (i < (LockSpinCount + LockSleep0Count)) { RuntimeThread.Sleep(0); // Give up my quantum. } else { RuntimeThread.Sleep(1); // Give up my quantum. } if (_myLock == 0 && Interlocked.CompareExchange(ref _myLock, 1, 0) == 0) { return; } } }
// TODO: CoreCLR: Worker Tracking in CoreCLR? (Config name: ThreadPool_EnableWorkerTracking) private static void GateThreadStart() { AppContext.TryGetSwitch("System.Threading.ThreadPool.DisableStarvationDetection", out bool disableStarvationDetection); AppContext.TryGetSwitch("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", out bool debuggerBreakOnWorkStarvation); while (true) { RuntimeThread.Sleep(GateThreadDelayMs); if (ThreadPoolInstance._numRequestedWorkers > 0) { WorkerThread.MaybeAddWorkingWorker(); } if (!s_requested) { continue; } s_requested = false; ThreadPoolInstance._cpuUtilization = s_cpu.CurrentUtilization; if (!disableStarvationDetection) { if (ThreadPoolInstance._numRequestedWorkers > 0 && SufficientDelaySinceLastDequeue()) { try { ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Acquire(); ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); // don't add a thread if we're at max or if we are already in the process of adding threads while (counts.numExistingThreads < ThreadPoolInstance._maxThreads && counts.numExistingThreads >= counts.numThreadsGoal) { if (debuggerBreakOnWorkStarvation) { Debug.WriteLine("The CLR ThreadPool detected work starvation!"); Debugger.Break(); } ThreadCounts newCounts = counts; newCounts.numThreadsGoal = (short)(newCounts.numExistingThreads + 1); ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, counts); if (oldCounts == counts) { HillClimbing.ThreadPoolHillClimber.ForceChange(newCounts.numThreadsGoal, HillClimbing.StateOrTransition.Starvation); WorkerThread.MaybeAddWorkingWorker(); break; } counts = oldCounts; } } finally { ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Release(); } } } } }
/// <summary> /// Performs a single spin. /// </summary> /// <remarks> /// This is typically called in a loop, and may change in behavior based on the number of times a /// <see cref="SpinOnce"/> has been called thus far on this instance. /// </remarks> public void SpinOnce() { if (NextSpinWillYield) { // // We must yield. // // We prefer to call Thread.Yield first, triggering a SwitchToThread. This // unfortunately doesn't consider all runnable threads on all OS SKUs. In // some cases, it may only consult the runnable threads whose ideal processor // is the one currently executing code. Thus we ocassionally issue a call to // Sleep(0), which considers all runnable threads at equal priority. Even this // is insufficient since we may be spin waiting for lower priority threads to // execute; we therefore must call Sleep(1) once in a while too, which considers // all runnable threads, regardless of ideal processor and priority, but may // remove the thread from the scheduler's queue for 10+ms, if the system is // configured to use the (default) coarse-grained system timer. // int yieldsSoFar = (_count >= YIELD_THRESHOLD ? _count - YIELD_THRESHOLD : _count); if ((yieldsSoFar % SLEEP_1_EVERY_HOW_MANY_TIMES) == (SLEEP_1_EVERY_HOW_MANY_TIMES - 1)) { RuntimeThread.Sleep(1); } else if ((yieldsSoFar % SLEEP_0_EVERY_HOW_MANY_TIMES) == (SLEEP_0_EVERY_HOW_MANY_TIMES - 1)) { RuntimeThread.Sleep(0); } else { RuntimeThread.Yield(); } } else { // // Otherwise, we will spin. // // We do this using the CLR's SpinWait API, which is just a busy loop that // issues YIELD/PAUSE instructions to ensure multi-threaded CPUs can react // intelligently to avoid starving. (These are NOOPs on other CPUs.) We // choose a number for the loop iteration count such that each successive // call spins for longer, to reduce cache contention. We cap the total // number of spins we are willing to tolerate to reduce delay to the caller, // since we expect most callers will eventually block anyway. // RuntimeThread.SpinWait(4 << _count); } // Finally, increment our spin counter. _count = (_count == int.MaxValue ? YIELD_THRESHOLD : _count + 1); }
// TODO: CoreCLR: Worker Tracking in CoreCLR? (Config name: ThreadPool_EnableWorkerTracking) private static void GateThreadStart() { var initialCpuRead = s_cpu.CurrentUtilization; // The first reading is over a time range other than what we are focusing on, so we do not use the read. AppContext.TryGetSwitch("System.Threading.ThreadPool.DisableStarvationDetection", out bool disableStarvationDetection); AppContext.TryGetSwitch("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", out bool debuggerBreakOnWorkStarvation); while (true) { s_runGateThreadEvent.WaitOne(); do { RuntimeThread.Sleep(GateThreadDelayMs); ThreadPoolInstance._cpuUtilization = s_cpu.CurrentUtilization; if (!disableStarvationDetection) { if (ThreadPoolInstance._numRequestedWorkers > 0 && SufficientDelaySinceLastDequeue()) { try { ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Acquire(); ThreadCounts counts = ThreadCounts.VolatileReadCounts(ref ThreadPoolInstance._separated.counts); // don't add a thread if we're at max or if we are already in the process of adding threads while (counts.numExistingThreads < ThreadPoolInstance._maxThreads && counts.numExistingThreads >= counts.numThreadsGoal) { if (debuggerBreakOnWorkStarvation) { Debugger.Break(); } ThreadCounts newCounts = counts; newCounts.numThreadsGoal = (short)(newCounts.numExistingThreads + 1); ThreadCounts oldCounts = ThreadCounts.CompareExchangeCounts(ref ThreadPoolInstance._separated.counts, newCounts, counts); if (oldCounts == counts) { HillClimbing.ThreadPoolHillClimber.ForceChange(newCounts.numThreadsGoal, HillClimbing.StateOrTransition.Starvation); WorkerThread.MaybeAddWorkingWorker(); break; } counts = oldCounts; } } finally { ThreadPoolInstance._hillClimbingThreadAdjustmentLock.Release(); } } } } while (ThreadPoolInstance._numRequestedWorkers > 0 || Interlocked.Decrement(ref s_runningState) > GetRunningStateForNumRuns(0)); } }
private static void SpinWait(int SpinCount) { //Exponential back-off if ((SpinCount < 5) && (Environment.ProcessorCount > 1)) { RuntimeThread.SpinWait(LockSpinCycles * SpinCount); } else if (SpinCount < MaxSpinCount - 3) { RuntimeThread.Sleep(0); } else { RuntimeThread.Sleep(1); } }
private static void SpinWait(int SpinCount) { //Exponential back-off if ((SpinCount < 5) && (Environment.ProcessorCount > 1)) { RuntimeThread.SpinWait(LockSpinCycles * SpinCount); } else { RuntimeThread.Sleep(0); } // Don't want to Sleep(1) in this spin wait: // - Don't want to spin for that long, since a proper wait will follow when the spin wait fails. The artifical // delay introduced by Sleep(1) will in some cases be much longer than desired. // - Sleep(1) would put the thread into a wait state, and a proper wait will follow when the spin wait fails // anyway, so it's preferable to put the thread into the proper wait state }
public static void Sleep(int millisecondsTimeout) => RuntimeThread.Sleep(millisecondsTimeout);
/// <summary> /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a /// 32-bit signed integer to measure the time interval, while observing a <see /// cref="T:System.Threading.CancellationToken"/>. /// </summary> /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param> /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to /// observe.</param> /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise, /// false.</returns> /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a /// negative number other than -1, which represents an infinite time-out.</exception> /// <exception cref="T:System.InvalidOperationException"> /// The maximum number of waiters has been exceeded. /// </exception> /// <exception cref="T:System.Threading.OperationCanceledException"><paramref /// name="cancellationToken"/> was canceled.</exception> public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) { ThrowIfDisposed(); cancellationToken.ThrowIfCancellationRequested(); // an early convenience check if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout)); } if (!IsSet) { if (millisecondsTimeout == 0) { // For 0-timeouts, we just return immediately. return(false); } // We spin briefly before falling back to allocating and/or waiting on a true event. uint startTime = 0; bool bNeedTimeoutAdjustment = false; int realMillisecondsTimeout = millisecondsTimeout; //this will be adjusted if necessary. if (millisecondsTimeout != Timeout.Infinite) { // We will account for time spent spinning, so that we can decrement it from our // timeout. In most cases the time spent in this section will be negligible. But // we can't discount the possibility of our thread being switched out for a lengthy // period of time. The timeout adjustments only take effect when and if we actually // decide to block in the kernel below. startTime = TimeoutHelper.GetTime(); bNeedTimeoutAdjustment = true; } //spin int HOW_MANY_SPIN_BEFORE_YIELD = 10; int HOW_MANY_YIELD_EVERY_SLEEP_0 = 5; int HOW_MANY_YIELD_EVERY_SLEEP_1 = 20; int spinCount = SpinCount; for (int i = 0; i < spinCount; i++) { if (IsSet) { return(true); } else if (i < HOW_MANY_SPIN_BEFORE_YIELD) { if (i == HOW_MANY_SPIN_BEFORE_YIELD / 2) { RuntimeThread.Yield(); } else { RuntimeThread.SpinWait(PlatformHelper.ProcessorCount * (4 << i)); } } else if (i % HOW_MANY_YIELD_EVERY_SLEEP_1 == 0) { RuntimeThread.Sleep(1); } else if (i % HOW_MANY_YIELD_EVERY_SLEEP_0 == 0) { RuntimeThread.Sleep(0); } else { RuntimeThread.Yield(); } if (i >= 100 && i % 10 == 0) // check the cancellation token if the user passed a very large spin count { cancellationToken.ThrowIfCancellationRequested(); } } // Now enter the lock and wait. EnsureLockObjectCreated(); // We must register and deregister the token outside of the lock, to avoid deadlocks. using (cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCallback, this)) { using (LockHolder.Hold(m_lock)) { // Loop to cope with spurious wakeups from other waits being canceled while (!IsSet) { // If our token was canceled, we must throw and exit. cancellationToken.ThrowIfCancellationRequested(); //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled) if (bNeedTimeoutAdjustment) { realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout); if (realMillisecondsTimeout <= 0) { return(false); } } // There is a race that Set will fail to see that there are waiters as Set does not take the lock, // so after updating waiters, we must check IsSet again. // Also, we must ensure there cannot be any reordering of the assignment to Waiters and the // read from IsSet. This is guaranteed as Waiters{set;} involves an Interlocked.CompareExchange // operation which provides a full memory barrier. // If we see IsSet=false, then we are guaranteed that Set() will see that we are // waiting and will pulse the monitor correctly. Waiters = Waiters + 1; if (IsSet) //This check must occur after updating Waiters. { Waiters--; //revert the increment. return(true); } // Now finally perform the wait. try { // ** the actual wait ** if (!m_condition.Wait(realMillisecondsTimeout)) { return(false); //return immediately if the timeout has expired. } } finally { // Clean up: we're done waiting. Waiters = Waiters - 1; } // Now just loop back around, and the right thing will happen. Either: // 1. We had a spurious wake-up due to some other wait being canceled via a different cancellationToken (rewait) // or 2. the wait was successful. (the loop will break) } } } } // automatically disposes (and deregisters) the callback return(true); //done. The wait was satisfied. }
internal static void Sleep(int milliseconds) { RuntimeThread.Sleep(milliseconds); }
/// <summary> /// Try acquire the lock with long path, this is usually called after the first path in Enter and /// TryEnter failed The reason for short path is to make it inline in the run time which improves the /// performance. This method assumed that the parameter are validated in Enter ir TryENter method /// </summary> /// <param name="millisecondsTimeout">The timeout milliseconds</param> /// <param name="lockTaken">The lockTaken param</param> private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken) { // The fast path doesn't throw any exception, so we have to validate the parameters here if (lockTaken) { lockTaken = false; throw new System.ArgumentException(SR.SpinLock_TryReliableEnter_ArgumentException); } if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException( nameof(millisecondsTimeout), millisecondsTimeout, SR.SpinLock_TryEnter_ArgumentOutOfRange); } uint startTime = 0; if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0) { startTime = TimeoutHelper.GetTime(); } if (SpinLockTrace.Enabled) { SpinLockTrace.SpinLock_FastPathFailed(m_owner); } if (IsThreadOwnerTrackingEnabled) { // Slow path for enabled thread tracking mode ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTime, ref lockTaken); return; } // then thread tracking is disabled // In this case there are three ways to acquire the lock // 1- the first way the thread either tries to get the lock if it's free or updates the waiters, if the turn >= the processors count then go to 3 else go to 2 // 2- In this step the waiter threads spins and tries to acquire the lock, the number of spin iterations and spin count is dependent on the thread turn // the late the thread arrives the more it spins and less frequent it check the lock avilability // Also the spins count is increases each iteration // If the spins iterations finished and failed to acquire the lock, go to step 3 // 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1) // If the timeout is expired in after step 1, we need to decrement the waiters count before returning int observedOwner; int turn = int.MaxValue; //***Step 1, take the lock or update the waiters // try to acquire the lock directly if possible or update the waiters count observedOwner = m_owner; if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) { if (CompareExchange(ref m_owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner) { // Aquired lock return; } if (millisecondsTimeout == 0) { // Did not aquire lock in CompareExchange and timeout is 0 so fail fast return; } } else if (millisecondsTimeout == 0) { // Did not aquire lock as owned and timeout is 0 so fail fast return; } else //failed to acquire the lock,then try to update the waiters. If the waiters count reached the maximum, jsut break the loop to avoid overflow { if ((observedOwner & WAITERS_MASK) != MAXIMUM_WAITERS) { turn = (Interlocked.Add(ref m_owner, 2) & WAITERS_MASK) >> 1; } } //***Step 2. Spinning //lock acquired failed and waiters updated int processorCount = PlatformHelper.ProcessorCount; if (turn < processorCount) { int processFactor = 1; for (int i = 1; i <= turn * SPINNING_FACTOR; i++) { RuntimeThread.SpinWait((turn + i) * SPINNING_FACTOR * processFactor); if (processFactor < processorCount) { processFactor++; } observedOwner = m_owner; if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) { int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero observedOwner | 1 // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit Debug.Assert((newOwner & WAITERS_MASK) >= 0); if (CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner) { return; } } } // Check the timeout. if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0) { DecrementWaiters(); return; } } //*** Step 3, Yielding //Sleep(1) every 50 yields int yieldsoFar = 0; while (true) { observedOwner = m_owner; if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) { int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero observedOwner | 1 // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit Debug.Assert((newOwner & WAITERS_MASK) >= 0); if (CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner) { return; } } if (yieldsoFar % SLEEP_ONE_FREQUENCY == 0) { RuntimeThread.Sleep(1); } else if (yieldsoFar % SLEEP_ZERO_FREQUENCY == 0) { RuntimeThread.Sleep(0); } else { RuntimeThread.Yield(); } if (yieldsoFar % TIMEOUT_CHECK_FREQUENCY == 0) { //Check the timeout. if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0) { DecrementWaiters(); return; } } yieldsoFar++; } }