并发集合在没有Thread.Sleep的情况下吃太多cpu

时间:2011-07-01 09:17:09

标签: c# multithreading concurrent-collections

BlockingCollectionConcurrentQueue的正确用法是什么,这样您就可以自由地将项目出列,而不会使用线程烧掉一半或更多的CPU? < / p>

我正在使用2个线程运行一些测试,除非我的Thread.Sleep至少为50~100ms,否则它至少会占到我CPU的50%。

这是一个虚构的例子:

private void _DequeueItem()
{
    object o = null;
    while(socket.Connected)
    {
        while (!listOfQueueItems.IsEmpty)
        {
            if (listOfQueueItems.TryDequeue(out o))
            {
                // use the data
            }
        }
    }
}

通过上面的例子,我必须设置一个thread.sleep,这样cpu就不会爆炸了。

注意:我也尝试了没有用于IsEmpty检查的时间,结果是一样的。

3 个答案:

答案 0 :(得分:23)

这不是因为BlockingCollectionConcurrentQueue,而是因为while循环:

while(socket.Connected)
{
    while (!listOfQueueItems.IsEmpty)
    { /*code*/ }
}

当然会降低cpu;因为如果队列是空的,那么while循环就像:

while (true) ;

反过来会占用cpu资源。

这不是使用ConcurrentQueue的好方法,您应该使用AutoResetEvent,因此无论何时添加项目,您都会收到通知。 例如:

private ConcurrentQueue<Data> _queue = new ConcurrentQueue<Data>();
private AutoResetEvent _queueNotifier = new AutoResetEvent(false);

//at the producer:
_queue.Enqueue(new Data());
_queueNotifier.Set();

//at the consumer:
while (true)//or some condition
{
    _queueNotifier.WaitOne();//here we will block until receive signal notification.
    Data data;
    if (_queue.TryDequeue(out data))
    {
        //handle the data
    }
}

为了更好地使用BlockingCollection,您应该使用GetConsumingEnumerable()等待添加的项目,例如:

//declare the buffer
private BlockingCollection<Data> _buffer = new BlockingCollection<Data>(new ConcurrentQueue<Data>());

//at the producer method:
_messageBuffer.Add(new Data());

//at the consumer
foreach (Data data in _buffer.GetConsumingEnumerable())//it will block here automatically waiting from new items to be added and it will not take cpu down 
{
    //handle the data here.
}

答案 1 :(得分:7)

在这种情况下,你真的想要使用BlockingCollection类。它被设计为阻塞,直到项目出现在队列中。这种性质的集合通常被称为阻塞队列。对于多个生产者多个消费者而言,此特定实施是安全的。如果你自己尝试实现它,那就很难做到。如果您使用BlockingCollection,则以下是您的代码。

private void _DequeueItem()
{
    while(socket.Connected)
    {
        object o = listOfQueueItems.Take();
        // use the data
    }
}

如果队列为空,Take方法会自动阻止。它以一种将线程置于SleepWaitJoin状态的方式阻塞,这样它就不会消耗CPU资源。关于BlockingCollection的巧妙之处在于它还使用低锁策略来提高性能。这意味着Take将检查队列中是否有项目,如果没有,则简要执行旋转等待以防止线程的上下文切换。如果队列仍为空,那么它将使线程进入休眠状态。这意味着BlockingCollection将具有ConcurrentQueue在并发执行方面提供的一些性能优势。

答案 2 :(得分:0)

只有当队列为空时才能调用Thread.Sleep()

private void DequeueItem()
{
    object o = null;

    while(socket.Connected)
    {
        if (listOfQueueItems.IsEmpty)
        {
            Thread.Sleep(50);
        }
        else if (listOfQueueItems.TryDequeue(out o))
        {
            // use the data
        }
    }
}

否则你应该考虑使用事件。