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

Pri*_*rix 12 c# multithreading concurrent-collections

什么是正确的使用,BlockingCollection或者ConcurrentQueue你可以自由地出列项目,而不会使用一个线程烧掉一半或更多的CPU?

我使用2个线程运行一些测试,除非我有一个至少50~100ms的Thread.Sleep,它总是至少达到我CPU的50%.

这是一个虚构的例子:

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

通过上面的例子,我将不得不设置一个thread.sleep,以便cpu不会爆炸.

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

Jal*_*aid 22

这不是因为BlockingCollectionor ConcurrentQueue,而是while循环:

while(socket.Connected)
{
    while (!listOfQueueItems.IsEmpty)
    { /*code*/ }
}
Run Code Online (Sandbox Code Playgroud)

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

while (true) ;
Run Code Online (Sandbox Code Playgroud)

反过来会占用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
    }
}
Run Code Online (Sandbox Code Playgroud)

为了更好地使用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.
}
Run Code Online (Sandbox Code Playgroud)


Bri*_*eon 7

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

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

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