自定义线程池C#问题

时间:2014-07-17 09:22:27

标签: c# .net multithreading thread-safety

所以,我有一个自定义线程池,它接收最大数量的线程,你可以在其中排队项目。池将执行队列中的项目。问题是这个池的行为总是不同的。我创建了一个测试,它给这个池提供了X个操作,并等待池完成工作(这个等待有一个限制,但是限制足以让所有操作成功结束)。问题是,有时测试会返回“成功”响应,但大多数情况下它会超出时间限制和/或不会处理所有操作。

代码:

CustomThreadPool.cs

    public class CustomThreadPool : IDisposable
    {
        #region Private Members

        private readonly Thread m_checkThread;

        #endregion

        #region Public Properties

        public int MaxNumberOfThreads { get; set; }
        private readonly object m_lock = new object();
        private int m_currentNumberOfThreads;
        public int CurrentNumberOfThreads
        {
            get
            {
                lock (m_lock)
                {
                    return m_currentNumberOfThreads;
                }
            }
            private set
            {
                lock (m_lock)
                {
                    m_currentNumberOfThreads = value;
                }
            }
        }
        public ConcurrentQueue<WorkItem> QueuedItems { get; set; }

        #endregion

        #region Constructor

        public CustomThreadPool(int maxNumberOfThreads)
        {
            MaxNumberOfThreads = maxNumberOfThreads;

            QueuedItems = new ConcurrentQueue<WorkItem>();
            m_checkThread = new Thread(CheckThread);
            m_checkThread.Start();
        }

        #endregion

        #region Public Methods

        public void QueueItem(object argument, Action<WorkItem> method, string token = "")
        {
            QueuedItems.Enqueue(new WorkItem { Argument = argument, Method = method, Token = token });
        }

        public List<WorkItem> Stop()
        {
            m_checkThread.Abort();
            List<WorkItem> result = new List<WorkItem>();
            while (QueuedItems.Count > 0)
            {
                WorkItem wi;
                QueuedItems.TryDequeue(out wi);
                if (wi != null)
                    result.Add(wi);
            }
            CurrentNumberOfThreads = 0;
            return result;
        }

        #endregion

        #region Private Methods

        // ReSharper disable once FunctionNeverReturns
        private void CheckThread()
        {
            while (true)
            {
                if (CurrentNumberOfThreads >= MaxNumberOfThreads || QueuedItems.Count == 0)
                {
                    Thread.Yield();
                }

                int availableThreads = MaxNumberOfThreads - CurrentNumberOfThreads;

                List<WorkItem> toBeProcessed = new List<WorkItem>();

                for (var i = 0; i < availableThreads; i++)
                {
                    WorkItem wi;
                    QueuedItems.TryDequeue(out wi);

                    if (wi != null)
                    {
                        toBeProcessed.Add(wi);
                    }
                }

                foreach (WorkItem item in toBeProcessed)
                {
                    CurrentNumberOfThreads++;
                    item.ExecutingThread = new Thread(ProcessItem);
                    item.ExecutingThread.Start(item);
                }

                Thread.Sleep(50);
            }
        }

        private void ProcessItem(object wi)
        {
            WorkItem item = (WorkItem)wi;
            item.Method.Invoke(item);
            CurrentNumberOfThreads--;
            item.ExecutingThread.Abort();
        }

        #endregion

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                m_checkThread.Abort();
            }
        }
    }

测试:

private List<int> EndedOperations = new List<int>();
private List<int> StartedOperations = new List<int>();

public void CheckThreadPool()
    {
        int workTime = 100;
        int numberOfOperations = 100;
        int numberOfThreads = 10;
        int cycles = numberOfOperations / numberOfThreads;
        int totalTime = workTime * 2 * cycles;
        Stopwatch sw = new Stopwatch();
        sw.Start();
        CustomThreadPool pool = new CustomThreadPool(numberOfThreads);

        for (int i = 0; i < numberOfOperations; i++)
        {
            pool.QueueItem(new WorkInfo(i, workTime), DoWork);
        }
        Thread.Sleep(workTime);
        bool queueEmpty = false, operationsDone = false;
        while (pool.CurrentNumberOfThreads > 0 && sw.ElapsedMilliseconds < totalTime)
        {
            if (pool.QueuedItems.Count == 0 && !queueEmpty)
            {
                queueEmpty = true;
                Debug.WriteLine("Queue emptied at: {0}, operations left: {1}", sw.ElapsedMilliseconds, numberOfOperations - EndedOperations.Count);
            }
            if (EndedOperations.Count == numberOfOperations && !operationsDone)
            {
                operationsDone = true;
                Debug.WriteLine("Operations done at: {0}, number of threads: {1}", sw.ElapsedMilliseconds, pool.CurrentNumberOfThreads);
            }
            Thread.Yield();
        }
        sw.Stop();
        pool.Dispose();
        Thread.Sleep(workTime);
        Debug.WriteLine("Test ended with {0} unprocessed operations", numberOfOperations - EndedOperations.Count);
        for (int i = 0; i < numberOfOperations; i++)
        {
            if (!EndedOperations.Contains(i))
                Debug.WriteLine("Operation {0} was not fully processed", i);
            if (!StartedOperations.Contains(i))
                Debug.WriteLine("Operation {0} has never started", i);
        }
        Assert.IsTrue(sw.ElapsedMilliseconds < totalTime,
            string.Format(@"The pool did not stop in useful time. 
                                Remaining threads : {0}
                                Remaining queued items : {1}
                                Remaining operations: {2}",
                                pool.CurrentNumberOfThreads, pool.QueuedItems.Count, numberOfOperations - EndedOperations.Count));
        Assert.IsTrue(pool.QueuedItems.Count == 0,
            string.Format(@"Not all items were processed. 
                                Remaining : {0}
                                Processing time : {1}",
                                pool.QueuedItems.Count, sw.ElapsedMilliseconds));
    }

    private void DoWork(WorkItem wi)
    {
        WorkInfo info = (WorkInfo)wi.Argument;
        try
        {
           StartedOperations.Add(info.Id);
            Thread.Sleep(info.TestTime);
            EndedOperations.Add(info.Id);
        }
        catch(Exception ex)
        {
            Debug.WriteLine("id: {0}, ex: {1}", info.Id, ex.Message);
        }

    }

我假设问题可能来自共享资源,但我无法弄清楚哪一个。

谢谢。

1 个答案:

答案 0 :(得分:1)

即使你在lock的赋值上放了一个CurrentNumberOfThreads,这还不够,因为你的递增和递减操作不是原子的。

您应该使用Interlocked类提供的原子操作:

替换

CurrentNumberOfThreads++;

使用

System.Threading.Interlocked.Increment(ref m_CurrentNumberOfThreads);

CurrentNumberOfThreads--;

System.Threading.Interlocked.Decrement(ref m_CurrentNumberOfThreads);

另外需要注意的是:永远不要abort这样的线程。尽量让某些方式让他们得到通知并优雅地退出。