Mercurial > pub > ImplabNet
view Implab/Parallels/DispatchPool.cs @ 20:1c3b3d518480 promises
refactoring, sync
author | cin |
---|---|
date | Tue, 12 Nov 2013 02:27:22 +0400 |
parents | 7cd4a843b4e4 |
children | 6a56df4ec59e |
line wrap: on
line source
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Diagnostics; namespace Implab.Parallels { public abstract class DispatchPool<TUnit> : IDisposable { readonly int m_minThreads; readonly int m_maxThreads; int m_createdThreads = 0; int m_activeThreads = 0; int m_sleepingThreads = 0; int m_maxRunningThreads = 0; int m_exitRequired = 0; int m_releaseTimeout = 100; // timeout while the working thread will wait for the new tasks before exit AutoResetEvent m_hasTasks = new AutoResetEvent(false); protected DispatchPool(int min, int max) { if (min < 0) throw new ArgumentOutOfRangeException("min"); if (max <= 0) throw new ArgumentOutOfRangeException("max"); if (min > max) min = max; m_minThreads = min; m_maxThreads = max; } protected DispatchPool(int threads) : this(threads, threads) { } protected DispatchPool() { int maxThreads, maxCP; ThreadPool.GetMaxThreads(out maxThreads, out maxCP); m_minThreads = 0; m_maxThreads = maxThreads; } protected void InitPool() { for (int i = 0; i < m_minThreads; i++) StartWorker(); } public int PoolSize { get { return m_createdThreads; } } public int ActiveThreads { get { return m_activeThreads; } } public int MaxRunningThreads { get { return m_maxRunningThreads; } } protected bool IsDisposed { get { return m_exitRequired != 0; } } protected abstract bool TryDequeue(out TUnit unit); protected virtual bool ExtendPool() { if (m_sleepingThreads == 0) // no sleeping workers are available // try create one return StartWorker(); else { // we can get here a race condition when several threads asks to extend pool // and some sleaping threads are exited due timeout but they are still counted as sleeping // in that case all of this threads could exit except one WakePool(); return true; } } /// <summary> /// Запускает либо новый поток, если раньше не было ни одного потока, либо устанавливает событие пробуждение одного спящего потока /// </summary> protected void WakePool() { m_hasTasks.Set(); // wake sleeping thread; if (AllocateThreadSlot(1)) { // if there were no threads in the pool var worker = new Thread(this.Worker); worker.IsBackground = true; worker.Start(); } } bool Sleep(int timeout) { Interlocked.Increment(ref m_sleepingThreads); var result = m_hasTasks.WaitOne(timeout); Interlocked.Decrement(ref m_sleepingThreads); return result; } protected virtual bool Suspend() { //no tasks left, exit if the thread is no longer needed bool last; bool requestExit; if (m_releaseTimeout > 0) requestExit = !Sleep(m_releaseTimeout); else requestExit = true; if (requestExit && ReleaseThreadSlot(out last)) { // in case at the moment the last thread was being released // a new task was added to the queue, we need to try // to revoke the thread to avoid the situation when the task is left unprocessed if (last && m_hasTasks.WaitOne(0)) { if (AllocateThreadSlot(1)) return true; // spin again... else // we failed to reallocate the first slot for this thread // therefore we need to release the event m_hasTasks.Set(); } return false; } Sleep(-1); return true; } #region thread slots traits bool AllocateThreadSlot() { int current; // use spins to allocate slot for the new thread do { current = m_createdThreads; if (current >= m_maxThreads || m_exitRequired != 0) // no more slots left or the pool has been disposed return false; } while (current != Interlocked.CompareExchange(ref m_createdThreads, current + 1, current)); UpdateMaxThreads(current + 1); return true; } bool AllocateThreadSlot(int desired) { if (desired - 1 != Interlocked.CompareExchange(ref m_createdThreads, desired, desired - 1)) return false; UpdateMaxThreads(desired); return true; } bool ReleaseThreadSlot(out bool last) { last = false; int current; // use spins to release slot for the new thread do { current = m_createdThreads; if (current <= m_minThreads && m_exitRequired == 0) // the thread is reserved return false; } while (current != Interlocked.CompareExchange(ref m_createdThreads, current - 1, current)); last = (current == 1); return true; } /// <summary> /// releases thread slot unconditionally, used during cleanup /// </summary> /// <returns>true - no more threads left</returns> bool ReleaseThreadSlotAnyway() { var left = Interlocked.Decrement(ref m_createdThreads); return left == 0; } void UpdateMaxThreads(int count) { int max; do { max = m_maxRunningThreads; if (max >= count) break; } while(max != Interlocked.CompareExchange(ref m_maxRunningThreads, count, max)); } #endregion bool StartWorker() { if (AllocateThreadSlot()) { // slot successfully allocated var worker = new Thread(this.Worker); worker.IsBackground = true; worker.Start(); return true; } else { return false; } } bool FetchTask(out TUnit unit) { do { // exit if requested if (m_exitRequired != 0) { // release the thread slot Interlocked.Decrement(ref m_activeThreads); if (ReleaseThreadSlotAnyway()) // it was the last worker m_hasTasks.Dispose(); else m_hasTasks.Set(); // wake next worker unit = default(TUnit); return false; } // fetch task if (TryDequeue(out unit)) { ExtendPool(); return true; } Interlocked.Decrement(ref m_activeThreads); // entering suspend state // keep this thread and wait if (!Suspend()) return false; Interlocked.Increment(ref m_activeThreads); } while (true); } protected abstract void InvokeUnit(TUnit unit); void Worker() { TUnit unit; Interlocked.Increment(ref m_activeThreads); while (FetchTask(out unit)) InvokeUnit(unit); } protected virtual void Dispose(bool disposing) { if (disposing) { if (m_exitRequired == 0) { if (Interlocked.CompareExchange(ref m_exitRequired, 1, 0) != 0) return; // wake sleeping threads m_hasTasks.Set(); GC.SuppressFinalize(this); } } } public void Dispose() { Dispose(true); } ~DispatchPool() { Dispose(false); } } }