最高效的CPU方法是什么,可以使工作线程等待任务?

5
在我的C#/NET 3.5应用程序中,我有一个任务队列(线程安全),我有5个工作线程必须不断地查找队列中的任务。如果有任务可用,任何一个工作线程都会出列任务并采取所需行动。
我的工作线程类如下:
public class WorkerThread
{
    //ConcurrentQueue is my implementation of thread safe queue
    //Essentially just a wrapper around Queue<T> with synchronization locks
    readonly ConcurrentQueue<CheckPrimeTask> mQ; 
    readonly Thread mWorker;
    bool mStop;

    public WorkerThread (ConcurrentQueue<CheckPrimeTask> aQ) {
        mQ = aQ;
        mWorker = new Thread (Work) {IsBackground = true};
        mStop = false;
    }

    private void Work () {
        while (!mStop) {
            if (mQ.Count == 0) {
                Thread.Sleep (0);
                continue;
            }

            var task = mQ.Dequeue ();
            //Someone else might have been lucky in stealing
            //the task by the time we dequeued it!!
            if (task == null) 
                continue;

            task.IsPrime = IsPrime (task.Number);
            task.ExecutedBy = Thread.CurrentThread.ManagedThreadId;
            //Ask the threadpool to execute the task callback to 
            //notify completion
            ThreadPool.QueueUserWorkItem (task.CallBack, task);
        }
    }

    private bool IsPrime (int number) {
        int limit = Convert.ToInt32 (Math.Sqrt (number));
        for (int i = 2; i <= limit; i++) {
            if (number % i == 0)
                return false;
        }

        return true;
    }

    public void Start () {
        mStop = false;
        mWorker.Start ();
    }

    public void Stop () {
        mStop = true;
    }
}

问题是当队列为空时,它会消耗太多的CPU(近98%)。我尝试使用AutoResetEvent来通知工作线程队列已经改变。因此,它们有效地等待该信号来设置。这将CPU降至接近0%,但我并不完全确定这是否是最佳方法。您能否建议一种更好的方法,使线程保持空闲而不会影响CPU使用率?


3
使用重置事件是最佳方法。 - Mitch Wheat
2
另一种可能性是使用ThreadPool.QueueUserWorkItem。 - Mitch Wheat
另一种方法是使用定时器检查队列并启动工作线程。 - Ed Power
4个回答

7

看看这个实现BlockingQueue的方法。如果队列为空,它使用Monitor.Wait()使线程进入睡眠状态。当添加一个项目时,使用Monitor.Pulse()唤醒正在等待队列为空的线程。

另一种技术是使用Semaphore。每次向队列中添加一个项目时,请调用Release()。当需要从队列中获取一个项目时,请调用WaitOne()。


我喜欢 Semaphore 的想法,因为它确保只有一个线程被唤醒,当一个项目被添加到队列中。使用重置事件会唤醒所有工作线程。谢谢。 - Hemant
但是它有一个缺点,我必须在创建信号量时指定队列中允许的最大项目数!信号量是否足够高效以处理非常大的计数? - Hemant
作为对第一条评论的回应。Monitor.Pulse只唤醒一个线程,(Monitor.PulseAll)会唤醒所有等待的线程,所以它们之间没有区别。 - R Samuel Klatchko
只需将最大可能值设置为 Int32.MaxValue。 new Semaphore(0, Int32.MaxValue) - R Samuel Klatchko
记录一下:我使用了信号量技术,效果非常好!谢谢大家。 - Hemant

2

在没有队列项目的情况下,您的Work方法中当前使用了Thread.Sleep(0)。将其更改为任何大于0的值,您的CPU使用率将会降低。建议从10开始尝试...


0
如果您的队列是线程安全的,那么您就不需要这样做...
    //Someone else might have been lucky in stealing 
    //the task by the time we dequeued it!! 
    if (task == null)  
        continue; 

0
你有几个选项可以考虑。
一种方法是在循环期间放置一个小的线程休眠。这将基本上将CPU使用率降至0,并且是相当标准的做法。
另一种方法是使用重置(自动或手动),如Mitch Wheat在评论中建议的那样。
你还可以设计某种IdleTask,它具有一定量的线程休眠时间,如果你的队列为空,只需处理IdleTask(它将线程休眠)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接