C# Следует ли мне использовать оператор блокировки, когда я только читаю и не меняю очередь? - PullRequest
0 голосов
/ 28 мая 2020

Я использую список в качестве очереди в моем многопоточном приложении C#. Я блокирую свои собственные методы Enqueue и Dequeue. Потому что несколько потоков могут вызывать эти методы.

From: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement Я думаю, что должен, но все равно задам вопрос здесь. Должен ли я заблокировать метод Peek, который только сообщает мне, какой элемент имеет нулевой индекс? Для примера рассмотрим, что у меня есть список (очередь) целых чисел.

list = [1, 2, 2, 5]
Enqueue(list, 6)
// [1, 2, 2, 5, 6]

var element = Dequeue(list) // element = 1;
// [2, 2, 5, 6]

var peekElement = Peek(list) // peekElement = 2
// [2, 2, 5, 6]

Ответы [ 5 ]

4 голосов
/ 28 мая 2020

Почему бы не использовать ConcurrentQueue, который легко выполнит вашу работу?

var queue = new ConcurrentQueue<int>();

queue.Enqueue(1);
queue.Enqueue(2);

if (queue.TryPeek(out int firstValue))
    Console.WriteLine("Peek: " + firstValue);

if (queue.TryDequeue(out int result))
    Console.WriteLine("Dequeue: " + result);

https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1?view=netcore-3.1

3 голосов
/ 28 мая 2020

Вы должны заблокировать.

Я полагаю, что ваш Peek() метод выглядит примерно так:

public int Peek(List<int> list)
{
    if (list.Length < 1)
        throw new InvalidOperationException("Queue is empty");

    return list[0];
}

Вы ожидаете, что он выдаст InvalidOperationException с сообщением «Очередь empty ", если вы попытаетесь заглянуть в пустую очередь.

Но теперь представьте себе этот сценарий с двумя потоками, A и B:

  1. Поток A вызывает Peek () для списка с один элемент.
  2. Поток A выполняет if (list.Length < 1) и определяет, что список не пуст, поэтому он не генерирует исключение.
  3. Поток B вызывает Dequeue () и очищает список.
  4. Поток A переходит к return list[0] и упс - список пуст, поэтому вы получаете совершенно другое исключение ArgumentOutOfRangeException.
2 голосов
/ 28 мая 2020

Должен ли я заблокировать метод Peek ...?

Если вы не прислушиваетесь к совету Абхая (см. Другой ответ), тогда да.

Настоящая цель блокировка блокировки - это гарантия того, что поток, который только что заблокировал блокировку, увидит общие переменные в состоянии, которое согласуется с тем, как они были оставлены каким-то другим потоком, изменившим их, прежде чем он освободил ту же блокировку.

Переменная list в вашем примере имеет некоторое внутреннее представление, которое может быть более сложным, чем простой массив. Если вы разрешите какому-либо потоку R просматривать list без блокировки блокировки, когда какой-либо другой поток W может изменить список в любое время, вы не просто рискуете увидеть поток R какой-то устаревшей версии списка; вы также рискуете, что поток R увидит поврежденную версию списка.

Даже такая простая вещь, как запрос длины списка, может привести к тому, что поток R последует за неверным указателем и cra sh программа, или хуже.

1 голос
/ 29 мая 2020

Из документации класса Queue<T>:

Publi c stati c Члены этого типа являются потокобезопасными. Потокобезопасность любых членов экземпляра не гарантируется.

A Queue<T> может поддерживать несколько считывателей одновременно, если коллекция не изменяется. Но даже в этом случае перечисление через коллекцию по своей сути не является поточно-ориентированной процедурой. Для потокобезопасной очереди см. ConcurrentQueue<T>.

Итак, если вы вызываете Peek из одного потока, в то время как очередь изменяется одновременно другими потоками без правильной синхронизации доступ к очереди с использованием lock или других средств, вы нарушаете гарантии, предлагаемые производителем класса. Поведение класса официально становится неопределенным. Это не лучшая идея, если вы пытаетесь создать программу, которая должна быть надежной в отношении правильности своих результатов.

1 голос
/ 28 мая 2020

Нет, не следует.

Вы должны использовать модифицированную блокировку, которая не является оператором - MUltipleReaderSingleWriter.

Он допускает несколько чтений, но только ОДНУ запись и только когда нет активных читателей.

...