我的自定义线程池有什么问题?

时间:2009-01-19 13:24:54

标签: c# .net multithreading lambda

我已经创建了一个自定义线程池实用程序,但似乎有一个我找不到的问题。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

namespace iWallpaper.S3Uploader
{
public class QueueManager<T>
{
    private readonly Queue queue = Queue.Synchronized(new Queue());
    private readonly AutoResetEvent res = new AutoResetEvent(true);
    private readonly AutoResetEvent res_thr = new AutoResetEvent(true);
    private readonly Semaphore sem = new Semaphore(1, 4);
    private readonly Thread thread;
    private Action<T> DoWork;
    private int Num_Of_Threads;

    private QueueManager()
    {
        Num_Of_Threads = 0;
        maxThread = 5;
        thread = new Thread(Worker) {Name = "S3Uploader EventRegisterer"};
        thread.Start();

        //   log.Info(String.Format("{0} [QUEUE] FileUploadQueueManager created", DateTime.Now.ToLongTimeString()));
    }

    public int maxThread { get; set; }

    public static FileUploadQueueManager<T> Instance
    {
        get { return Nested.instance; }
    }

    /// <summary>
    /// Executes multythreaded operation under items
    /// </summary>
    /// <param name="list">List of items to proceed</param>
    /// <param name="action">Action under item</param>
    /// <param name="MaxThreads">Maximum threads</param>
    public void Execute(IEnumerable<T> list, Action<T> action, int MaxThreads)
    {
        maxThread = MaxThreads;
        DoWork = action;
        foreach (T item in list)
        {
            Add(item);
        }
    }
    public void ExecuteNoThread(IEnumerable<T> list, Action<T> action)
    {
        ExecuteNoThread(list, action, 0);
    }
    public void ExecuteNoThread(IEnumerable<T> list, Action<T> action, int MaxThreads)
    {
        foreach (T wallpaper in list)
        {
            action(wallpaper);
        }
    }
    /// <summary>
    /// Default 10 threads
    /// </summary>
    /// <param name="list"></param>
    /// <param name="action"></param>
    public void Execute(IEnumerable<T> list, Action<T> action)
    {
        Execute(list, action, 10);
    }

    private void Add(T item)
    {
        lock (queue)
        {
            queue.Enqueue(item);
        }
        res.Set();
    }

    private void Worker()
    {
        while (true)
        {
            if (queue.Count == 0)
            {
                res.WaitOne();
            }

            if (Num_Of_Threads < maxThread)
            {
                var t = new Thread(Proceed);
                t.Start();
            }
            else
            {
                res_thr.WaitOne();
            }
        }
    }

    private void Proceed()
    {
        Interlocked.Increment(ref Num_Of_Threads);
        if (queue.Count > 0)
        {
            var item = (T) queue.Dequeue();

            sem.WaitOne();
            ProceedItem(item);
            sem.Release();
        }
        res_thr.Set();
        Interlocked.Decrement(ref Num_Of_Threads);
    }

    private void ProceedItem(T activity)
    {
        if (DoWork != null)
            DoWork(activity);

        lock (Instance)
        {
            Console.Title = string.Format("ThrId:{0}/{4}, {1}, Activity({2} left):{3}",
                                          thread.ManagedThreadId, DateTime.Now, queue.Count, activity,
                                          Num_Of_Threads);
        }
    }

    #region Nested type: Nested

    protected class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        internal static readonly QueueManager<T> instance = new FileUploadQueueManager<T>();
    }

    #endregion

}

}

问题出在这里:

Console.Title = string.Format("ThrId:{0}/{4}, {1}, Activity({2} left):{3}",
                                      thread.ManagedThreadId, DateTime.Now, queue.Count, activity,
                                      Num_Of_Threads);

标题中总是有一个线程ID。程序似乎在一个线程中工作。

样本用法:

        var i_list = new int[] {1, 2, 4, 5, 6, 7, 8, 6};
        QueueManager<int>.Instance.Execute(i_list,
          i =>
          {
              Console.WriteLine("Some action under element number {0}", i);

          }, 5);

P.S。:它非常混乱,但我还在努力。

4 个答案:

答案 0 :(得分:5)

我查看了你的代码,这里有几个问题。

  1. 即使它是同步队列,也会锁定队列对象。这是不必要的
  2. 您不一致地锁定队列对象。它应该为每次访问锁定或不锁定,具体取决于同步行为。
  3. Proceed方法不是线程安全的。这两行是问题

        if (queue.Count > 0) {
          var item = (T)queue.Dequeue();
        ...
        }
    

    使用同步队列仅保证单个访问是安全的。因此.Count和.Dequeue方法都不会混淆队列的内部结构。但是想象一下两个线程同时使用count 1队列运行这些代码行的情况

    • 线程1:if(...) - &gt;真
    • 线程2:if(...) - &gt;真
    • Thread1:dequeue - &gt; sucess
    • 线程2:出队 - &gt;由于队列为空而失败
  4. Worker和Proceed之间存在竞争条件,可能导致死锁。应该切换以下两行代码。

    代码:

        res_thr.Set()
        Interlocked.Decrement(ref Num_Of_Threads);

    第一行将取消阻止Worker方法。如果它运行得足够快,它将返回到外观,注意Num_Of_Threads&lt; maxThreads并返回res_thr.WaitOne()。如果当前没有其他线程正在运行,那么这将导致代码中出现死锁。使用较少的最大线程数(例如1)非常容易命中。反转这两行代码应该可以解决问题。

  5. maxThread count属性在4之后似乎没有用.sem对象初始化为只接受4个最大并发条目。实际执行项目的所有代码都必须通过此信号量。因此,无论maxThread设置多高,您都有效地将最大并发项数限制为4。

答案 1 :(得分:4)

编写健壮的线程代码并非易事。您可能会看到许多线程池以供参考,但另请注意,Parallel Extensions(可用作CTP或更高版本的.NET 4.0)包含许多其他线程构造。框(在TPL / CCR中)。例如,Parallel.For / Parallel.ForEach,它处理工作窃取,并有效地处理可用的核心。

有关预卷线程池的示例,请参阅Jon Skeet的CustomThreadPool here

答案 2 :(得分:2)

我认为你可以做很多事情。

这是我使用的线程池的修改后的表单(我没有测试修改):

唯一的同步。你需要的原始是一个监视器,锁定在线程池上。您不需要信号量或重置事件。

internal class ThreadPool
{
    private readonly Thread[] m_threads;
    private readonly Queue<Action> m_queue;
    private bool m_shutdown;
    private object m_lockObj;


    public ThreadPool(int numberOfThreads)
    {
        Util.Assume(numberOfThreads > 0, "Invalid thread count!");
        m_queue = new Queue<Action>();
        m_threads = new Thread[numberOfThreads];
        m_lockObj = new object();

        lock (m_lockObj)
        {
            for (int i = 0; i < numberOfWriteThreads; ++i)
            {
                m_threads[i] = new Thread(ThreadLoop);
                m_threads[i].Start();
            }
        }

    }

    public void Shutdown()
    {
        lock (m_lockObj)
        {
            m_shutdown = true;
            Monitor.PulseAll(m_lockObj);

            if (OnShuttingDown != null)
            {
                OnShuttingDown();
            }
        }
        foreach (var thread in m_threads)
        {
            thread.Join();
        }
    }
    public void Enqueue(Action a)
    {
        lock (m_lockObj)
        {
            m_queue.Enqueue(a);
            Monitor.Pulse(m_lockObj);
        }
    }

    private void ThreadLoop()
    {
        Monitor.Enter(m_lockObj);

        while (!m_shutdown)
        {
            if (m_queue.Count == 0)
            {
                Monitor.Wait(m_lockObj);
            }
            else
            {
                var a = m_queue.Dequeue();
                Monitor.Pulse(m_lockObj);
                Monitor.Exit(m_lockObj);
                try
                {
                    a();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("An unhandled exception occured!\n:{0}", ex.Message, null);
                }
                Monitor.Enter(m_lockObj);
            }
        }

        Monitor.Exit(m_lockObj);
    }
}

答案 3 :(得分:1)

您应该使用内置线程池。在运行你的代码时,我注意到你的一系列线程,但是由于队列计数<1,你只是退出,这一直持续到队列实际填充然后你的下一个线程处理所有内容。这是一个非常昂贵的过程。如果你有事可做,你应该只旋转线程。