Параллельные коллекции едят слишком много процессора без Thread.Sleep - PullRequest
12 голосов
/ 01 июля 2011

Как правильно использовать BlockingCollection или ConcurrentQueue, чтобы можно было свободно удалять элементы без необходимости прожигать половину или больше вашего ЦП с помощью потока?

Я выполнял некоторые тесты, используя 2 потока, и если бы у меня не было Thread.Sleep, по крайней мере, 50 ~ 100 мс, он всегда попадал бы по крайней мере на 50% моего процессора.1010 *

В приведенном выше примере мне нужно было бы установить thread.sleep, чтобы процессор не взрывался.

Примечание: я также пробовал это время без проверки IsEmpty, результат был таким же.

Ответы [ 3 ]

23 голосов
/ 01 июля 2011

Это не из-за BlockingCollection или ConcurrentQueue, а из-за цикла while:

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

Конечно, это опустит процессор; из-за того, что очередь пуста, цикл while выглядит так:

while (true) ;

, который в свою очередь съест ресурсы процессора.

Это не очень хороший способ использования 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.
}
7 голосов
/ 01 июля 2011

Вы действительно хотите использовать класс BlockingCollection в этом случае. Он предназначен для блокировки до появления элемента в очереди. Коллекция такого рода часто упоминается как блокирующая очередь. Эта конкретная реализация безопасна для нескольких производителей и для нескольких потребителей. Это то, что на удивление трудно понять правильно, если вы попытались реализовать это самостоятельно. Вот как бы выглядел ваш код, если бы вы использовали BlockingCollection.

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

Метод Take автоматически блокируется, если очередь пуста. Он блокируется таким образом, что переводит поток в состояние SleepWaitJoin, чтобы он не потреблял ресурсы процессора. Отличительной особенностью BlockingCollection является то, что он также использует стратегии низкого уровня блокировки для увеличения производительности. Это означает, что Take проверит наличие элемента в очереди, а если нет, то кратко выполнит ожидание вращения, чтобы предотвратить переключение контекста потока. Если очередь все еще пуста, то поток переходит в спящий режим. Это означает, что BlockingCollection будет иметь некоторые преимущества в производительности, которые ConcurrentQueue обеспечивает в отношении одновременного выполнения.

0 голосов
/ 01 июля 2011

Вы можете звонить 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
        }
    }
}

В противном случае вам следует рассмотреть возможность использования событий.

...