如何在.NET 3.5中重用线程

时间:2011-04-29 01:39:03

标签: c# .net multithreading threadpool

我有一个处理大块信息的子程序。为了利用整个CPU,它将工作分为单独的线程。所有线程完成后,它就完成了。我读到创建和销毁线程会占用大量开销,所以我尝试使用线程池,但实际上运行速度比创建自己的线程慢。如何在程序运行时创建自己的线程,然后继续重用它们?我见过有些人说它无法完成,但线程池是这样做的,所以它一定是可能的,对吗?

以下是启动新线程/使用线程池的代码的一部分:

//initialization for threads
Thread[] AltThread = null;
if (NumThreads > 1)
    AltThread = new Thread[pub.NumThreads - 1];

do
{
    if (NumThreads > 1)
    {   //split the matrix up into NumThreads number of even-sized blocks and execute on separate threads
        int ThreadWidth = DataWidth / NumThreads;
        if (UseThreadPool) //use threadpool threads
        {
            for (int i = 0; i < NumThreads - 1; i++)
            {
                ThreadPool.QueueUserWorkItem(ComputePartialDataOnThread, 
                    new object[] { AltEngine[i], ThreadWidth * (i + 1), ThreadWidth * (i + 2) });
            }
            //get number of threads available after queue
            System.Threading.Thread.Sleep(0);
            int StartThreads, empty, EndThreads;
            ThreadPool.GetAvailableThreads(out StartThreads, out empty);
            ComputePartialData(ThisEngine, 0, ThreadWidth);

            //wait for all threads to finish
            do
            {
                ThreadPool.GetAvailableThreads(out EndThreads, out empty);
                System.Threading.Thread.Sleep(1);
            } while (StartThreads - EndThreads > 0);
        }
        else //create new threads each time (can we reuse these?)
        {
            for (int i = 0; i < NumThreads - 1; i++)
            {
                AltThread[i] = new Thread(ComputePartialDataOnThread);
                AltThread[i].Start(new object[] { AltEngine[i], ThreadWidth * (i + 1), ThreadWidth * (i + 2) });
            }
            ComputePartialData(ThisEngine, 0, ThreadWidth);

            //wait for all threads to finish
            foreach (Thread t in AltThread)
                t.Join(1000);
            foreach (Thread t in AltThread)
                if (t.IsAlive) t.Abort();
        }
    }
}

ComputePartialDataOnThread只需解压缩信息并调用ComputePartialData。将要处理的数据在线程之间共享(它们不会尝试读取/写入相同的位置)。 AltEngine []是每个线程的独立计算引擎。

使用线程池运行大约10-20%。

3 个答案:

答案 0 :(得分:13)

这听起来像是一个相当普遍的要求,可以通过多线程生产者 - 消费者队列来解决。线程保持“活动”状态,并在将新工作添加到队列时发出信号以进行工作。工作由委托(在您的情况下为ComputePartialDataOnThread)表示,传递给委托的数据是排队的(在您的情况下是ComputePartialDataOnThread的参数)。有用的特性是管理工作线程和实际算法的实现是分开的。这是p-c队列:

public class SuperQueue<T> : IDisposable where T : class
{
    readonly object _locker = new object();
    readonly List<Thread> _workers;
    readonly Queue<T> _taskQueue = new Queue<T>();
    readonly Action<T> _dequeueAction;

    /// <summary>
    /// Initializes a new instance of the <see cref="SuperQueue{T}"/> class.
    /// </summary>
    /// <param name="workerCount">The worker count.</param>
    /// <param name="dequeueAction">The dequeue action.</param>
    public SuperQueue(int workerCount, Action<T> dequeueAction)
    {
        _dequeueAction = dequeueAction;
        _workers = new List<Thread>(workerCount);

        // Create and start a separate thread for each worker
        for (int i = 0; i < workerCount; i++)
        {
            Thread t = new Thread(Consume) { IsBackground = true, Name = string.Format("SuperQueue worker {0}",i )};
            _workers.Add(t);
            t.Start();
        }
    }

    /// <summary>
    /// Enqueues the task.
    /// </summary>
    /// <param name="task">The task.</param>
    public void EnqueueTask(T task)
    {
        lock (_locker)
        {
            _taskQueue.Enqueue(task);
            Monitor.PulseAll(_locker);
        }
    }

    /// <summary>
    /// Consumes this instance.
    /// </summary>
    void Consume()
    {
        while (true)
        {
            T item;
            lock (_locker)
            {
                while (_taskQueue.Count == 0) Monitor.Wait(_locker);
                item = _taskQueue.Dequeue();
            }
            if (item == null) return;

            // run actual method
            _dequeueAction(item);
        }
    }

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
        // Enqueue one null task per worker to make each exit.
        _workers.ForEach(thread => EnqueueTask(null));

        _workers.ForEach(thread => thread.Join());

    }
}

正如之前的海报所说,有许多内置结构(查看TPL),它们使用Threadpool,您可能希望在实现自己的队列之前查看它。

答案 1 :(得分:2)

所以通常的做法是让每个线程的入口点基本上做类似的事情(这只是一个算法,而不是C#代码,对不起):

  1. 检查您是否有工作要做
  2. 找到工作
  3. 等待信号
  4. 另一方面,只要你的线程有更多的工作,就把它添加到要做的工作队列中,然后你的线程就会被重用了。这非常类似于人们如何自己实现一个线程池(如果你在运行时你可以做一些其他事情来帮助你,但这不是一个超级大事)。

答案 2 :(得分:0)

这是一个讨论这件事的主题:A custom thread-pool/queue class.