internal bool NotifyWorkItemComplete() { // TODO: Check perf. Might need to make this thread-local. Interlocked.Increment(ref _completionCount); Volatile.Write(ref _separated.lastDequeueTime, Environment.TickCount); if (ShouldAdjustMaxWorkersActive()) { bool acquiredLock = _hillClimbingThreadAdjustmentLock.TryAcquire(); try { if (acquiredLock) { AdjustMaxWorkersActive(); } } finally { if (acquiredLock) { _hillClimbingThreadAdjustmentLock.Release(); } } } return(!WorkerThread.ShouldStopProcessingWorkNow()); }
internal bool NotifyWorkItemComplete() { _completionCounter.Increment(); Volatile.Write(ref _separated.lastDequeueTime, Environment.TickCount); if (ShouldAdjustMaxWorkersActive() && _hillClimbingThreadAdjustmentLock.TryAcquire()) { try { AdjustMaxWorkersActive(); } finally { _hillClimbingThreadAdjustmentLock.Release(); } } return(!WorkerThread.ShouldStopProcessingWorkNow()); }
// // This method must only be called if ShouldAdjustMaxWorkersActive has returned true, *and* // _hillClimbingThreadAdjustmentLock is held. // private void AdjustMaxWorkersActive() { LowLevelLock threadAdjustmentLock = _threadAdjustmentLock; if (!threadAdjustmentLock.TryAcquire()) { // The lock is held by someone else, they will take care of this for us return; } bool addWorker = false; try { // Repeated checks from ShouldAdjustMaxWorkersActive() inside the lock ThreadCounts counts = _separated.counts; if (counts.NumProcessingWork > counts.NumThreadsGoal || _pendingBlockingAdjustment != PendingBlockingAdjustment.None) { return; } long endTime = Stopwatch.GetTimestamp(); double elapsedSeconds = Stopwatch.GetElapsedTime(_currentSampleStartTime, endTime).TotalSeconds; if (elapsedSeconds * 1000 >= _threadAdjustmentIntervalMs / 2) { int currentTicks = Environment.TickCount; int totalNumCompletions = (int)_completionCounter.Count; int numCompletions = totalNumCompletions - _separated.priorCompletionCount; short oldNumThreadsGoal = counts.NumThreadsGoal; int newNumThreadsGoal; (newNumThreadsGoal, _threadAdjustmentIntervalMs) = HillClimbing.ThreadPoolHillClimber.Update(oldNumThreadsGoal, elapsedSeconds, numCompletions); if (oldNumThreadsGoal != (short)newNumThreadsGoal) { _separated.counts.InterlockedSetNumThreadsGoal((short)newNumThreadsGoal); // // If we're increasing the goal, inject a thread. If that thread finds work, it will inject // another thread, etc., until nobody finds work or we reach the new goal. // // If we're reducing the goal, whichever threads notice this first will sleep and timeout themselves. // if (newNumThreadsGoal > oldNumThreadsGoal) { addWorker = true; } } _separated.priorCompletionCount = totalNumCompletions; _separated.nextCompletedWorkRequestsTime = currentTicks + _threadAdjustmentIntervalMs; Volatile.Write(ref _separated.priorCompletedWorkRequestsTime, currentTicks); _currentSampleStartTime = endTime; } } finally { threadAdjustmentLock.Release(); } if (addWorker) { WorkerThread.MaybeAddWorkingWorker(this); } }
// // This method must only be called if ShouldAdjustMaxWorkersActive has returned true, *and* // _hillClimbingThreadAdjustmentLock is held. // private void AdjustMaxWorkersActive() { LowLevelLock hillClimbingThreadAdjustmentLock = _hillClimbingThreadAdjustmentLock; if (!hillClimbingThreadAdjustmentLock.TryAcquire()) { // The lock is held by someone else, they will take care of this for us return; } try { long startTime = _currentSampleStartTime; long endTime = Stopwatch.GetTimestamp(); long freq = Stopwatch.Frequency; double elapsedSeconds = (double)(endTime - startTime) / freq; if (elapsedSeconds * 1000 >= _threadAdjustmentIntervalMs / 2) { int currentTicks = Environment.TickCount; int totalNumCompletions = (int)_completionCounter.Count; int numCompletions = totalNumCompletions - _separated.priorCompletionCount; ThreadCounts currentCounts = _separated.counts.VolatileRead(); int newMax; (newMax, _threadAdjustmentIntervalMs) = HillClimbing.ThreadPoolHillClimber.Update(currentCounts.NumThreadsGoal, elapsedSeconds, numCompletions); while (newMax != currentCounts.NumThreadsGoal) { ThreadCounts newCounts = currentCounts; newCounts.NumThreadsGoal = (short)newMax; ThreadCounts oldCounts = _separated.counts.InterlockedCompareExchange(newCounts, currentCounts); if (oldCounts == currentCounts) { // // If we're increasing the max, inject a thread. If that thread finds work, it will inject // another thread, etc., until nobody finds work or we reach the new maximum. // // If we're reducing the max, whichever threads notice this first will sleep and timeout themselves. // if (newMax > oldCounts.NumThreadsGoal) { WorkerThread.MaybeAddWorkingWorker(this); } break; } if (oldCounts.NumThreadsGoal > currentCounts.NumThreadsGoal && oldCounts.NumThreadsGoal >= newMax) { // someone (probably the gate thread) increased the thread count more than // we are about to do. Don't interfere. break; } currentCounts = oldCounts; } _separated.priorCompletionCount = totalNumCompletions; _separated.nextCompletedWorkRequestsTime = currentTicks + _threadAdjustmentIntervalMs; Volatile.Write(ref _separated.priorCompletedWorkRequestsTime, currentTicks); _currentSampleStartTime = endTime; } } finally { hillClimbingThreadAdjustmentLock.Release(); } }
// // This method must only be called if ShouldAdjustMaxWorkersActive has returned true, *and* // _hillClimbingThreadAdjustmentLock is held. // private void AdjustMaxWorkersActive() { LowLevelLock threadAdjustmentLock = _threadAdjustmentLock; if (!threadAdjustmentLock.TryAcquire()) { // The lock is held by someone else, they will take care of this for us return; } bool addWorker = false; try { // Skip hill climbing when there is a pending blocking adjustment. Hill climbing may otherwise bypass the // blocking adjustment heuristics and increase the thread count too quickly. if (_pendingBlockingAdjustment != PendingBlockingAdjustment.None) { return; } long startTime = _currentSampleStartTime; long endTime = Stopwatch.GetTimestamp(); long freq = Stopwatch.Frequency; double elapsedSeconds = (double)(endTime - startTime) / freq; if (elapsedSeconds * 1000 >= _threadAdjustmentIntervalMs / 2) { int currentTicks = Environment.TickCount; int totalNumCompletions = (int)_completionCounter.Count; int numCompletions = totalNumCompletions - _separated.priorCompletionCount; int newNumThreadsGoal; (newNumThreadsGoal, _threadAdjustmentIntervalMs) = HillClimbing.ThreadPoolHillClimber.Update(_separated.numThreadsGoal, elapsedSeconds, numCompletions); short oldNumThreadsGoal = _separated.numThreadsGoal; if (oldNumThreadsGoal != (short)newNumThreadsGoal) { _separated.numThreadsGoal = (short)newNumThreadsGoal; // // If we're increasing the goal, inject a thread. If that thread finds work, it will inject // another thread, etc., until nobody finds work or we reach the new goal. // // If we're reducing the goal, whichever threads notice this first will sleep and timeout themselves. // if (newNumThreadsGoal > oldNumThreadsGoal) { addWorker = true; } } _separated.priorCompletionCount = totalNumCompletions; _separated.nextCompletedWorkRequestsTime = currentTicks + _threadAdjustmentIntervalMs; Volatile.Write(ref _separated.priorCompletedWorkRequestsTime, currentTicks); _currentSampleStartTime = endTime; } } finally { threadAdjustmentLock.Release(); } if (addWorker) { WorkerThread.MaybeAddWorkingWorker(this); } }