ConcurrentQueue <T>или Queue <T>, когда один поток только ставит в очередь, а другой поток только когда удаляет очередь - PullRequest
0 голосов
/ 29 апреля 2018

У меня есть FIFO и две темы. Один поток будет когда-либо только ставиться в очередь в FIFO, а другой поток будет только когда-либо ставиться в очередь из FIFO. Нужно ли брать ConcurrentQueue или достаточно очереди?

Ответы [ 2 ]

0 голосов
/ 29 апреля 2018

Краткий ответ: Да, вам все еще требуется поточно-ориентированное решение - даже если есть только один поток записи и один поток чтения.

Было бы проще использовать ConcurrentQueue. Вместо этого вы могли бы использовать Очередь, если хотите, но вам придется делать блокировку самостоятельно.

0 голосов
/ 29 апреля 2018

Если у вас более одного потока, для вашего экземпляра объекта Queue потребуется безопасность потока и синхронизация. Чтобы не изобретать велосипед и не делать это самостоятельно, я бы предложил использовать Microsoft ConcurrentQueue.

MSDN: https://docs.microsoft.com/en-us/dotnet/api/system.collections.queue?view=netframework-4.7.1

Используйте ConcurrentQueue или ConcurrentStack, если вам нужен доступ к сбор из нескольких потоков одновременно.

Если вы используете Queue (т.е. не ConcurrentQueue) с более чем одним потоком, обновляющим экземпляр объекта, вы можете столкнуться с исключениями времени выполнения, такими как:

  • ArgumentOutOfRangeException
  • ArgumentException (InvalidOffLen)
  • ExceptionResource.InvalidOperation_EmptyQueue

Исключения возможны, если внутреннее состояние очереди изменяется, но еще не завершено из-за планирования потоков ЦП. Если другой поток обращается к экземпляру объекта Queue, когда он находится в несогласованном состоянии, вы можете и, вероятно, столкнетесь с этими исключениями.

Исходный код для обзора:

.Net Framework 4.7.1 https://referencesource.microsoft.com/#System/compmod/system/collections/generic/queue.cs

Пример консольного приложения:

Запустите следующее в своей лаборатории, и вы должны столкнуться с System.InvalidOperationException

System.InvalidOperationException: 'Коллекция была изменена после создания экземпляра перечислителя.'

class Program
{
    static Queue<string> Queue = new Queue<string>();

    static void Main(string[] args)
    {
        Thread producer = new Thread(Enqueue);
        Thread consumer = new Thread(Dequeue);

        producer.Start();
        consumer.Start();

        Console.ReadKey();
    }

    static void Enqueue()
    {
        for (int i = 0; i < 10000; i++)
        {
            Queue.Enqueue("Number : " + i);
            SimulateWork();
        }
    }

    static void Dequeue()
    {
        while (true)
        {
            if (Queue.Any())
            {
                Console.WriteLine(Queue.Dequeue());
                SimulateWork();
            }
        }
    }

    static void SimulateWork()
    {
        for (int i = 0; i < 1000000; i++)
        { }
    }
}

Эта лабораторная работа демонстрирует, что может произойти, когда к экземпляру Queue обращаются в несовместимом состоянии. Даже с одним производителем и одним потребителем вам нужна правильная синхронизация.

Если вы добавите блокировку или синхронизацию вокруг операций Enqueue и Dequeue, вы заметите, что они работают без проблем.

            lock (lockObject)
            {
                Queue.Enqueue("Number : " + i);
                SimulateWork();
            }


            lock (lockObject)
            {
                if (Queue.Any())
                {
                    Console.WriteLine(Queue.Dequeue());
                    SimulateWork();
                }
            }

С учетом вышесказанного я НЕ предлагаю вам вручную добавить блокировки, которые будут блокировать. Это скорее лабораторное упражнение, которое поможет вам понять WHY .

Microsoft потратила много времени на предоставление нам многопоточных коллекций, таких как ConcurrentQueue, с использованием мелкозернистых блокировок и механизмов без блокировок.

Некоторые типы одновременных коллекций используют легкий механизмы синхронизации, такие как SpinLock, SpinWait, SemaphoreSlim, и CountdownEvent, которые являются новыми в .NET Framework 4. Эти Типы синхронизации обычно используют вращение в течение коротких периодов времени. прежде чем они поместят поток в настоящее состояние ожидания. Когда время ожидания как ожидается, будет очень коротким, вращение гораздо менее вычислительно дороже, чем ожидание, что включает в себя дорогой переход ядра. Для коллекционных классов, использующих спиннинг, эта эффективность означает, что несколько потоков могут добавлять и удалять элементы с очень высокой скоростью. За больше информации о вращении против блокировки, см. SpinLock и SpinWait.

Как указано выше, если у вас более одного потока, это потребует безопасности потока и синхронизации для вашего экземпляра объекта Queue. Чтобы не изобретать велосипед и не делать это самостоятельно, я бы весьма предложил бы использовать Microsoft ConcurrentQueue.

Рекомендации:

Потокобезопасные коллекции:

https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/

Блокировка по ключевому слову

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement

Threading:

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/threading/index

...