Monitor.Wait, переменная условия

Учитывая следующий фрагмент кода (найденный где-то при изучении потоковой передачи).

 public class BlockingQueue<T>
    {
        private readonly object sync = new object();
        private readonly Queue<T> queue;
        public BlockingQueue()
        {
            queue = new Queue<T>();
        }

        public void Enqueue(T item)
        {
            lock (sync)
            {
                queue.Enqueue(item);
                Monitor.PulseAll(sync);
            }

        }
        public T Dequeue()
        {
            lock (sync)
            {
                while (queue.Count == 0)
                    Monitor.Wait(sync);

                return queue.Dequeue();
            }

        }
    }

Я хочу понять,

Почему существует цикл while?

   while (queue.Count == 0)
            Monitor.Wait(sync);

и что не так с

 if(queue.Count == 0)
       Monitor.Wait(sync);

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

Ответ 1

Вам нужно понять, что делают Pulse, PulseAll и Wait. Monitor поддерживает две очереди: очередь ожидания и готовую очередь. Когда поток вызывает Wait, он перемещается в очередь ожидания. Когда поток вызывает Pulse, он перемещает один и только один поток из очереди ожидания в готовую очередь. Когда поток вызывает PulseAll, он перемещает все потоки из очереди ожидания в готовую очередь. Потоки в готовой очереди имеют право повторно зафиксировать блокировку в любой момент, но только после того, как текущий держатель освободит ее, конечно.

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

Итак, каков был бы вывод, если бы вы использовали Pulse вместо PulseAll? Там будет проблема с простой проверкой if. Причина в том, что поток из готовой очереди не обязательно будет следующим потоком для получения блокировки. Это связано с тем, что Monitor не отдает предпочтение вызову Wait над вызовом Enter.

Цикл while является довольно стандартным шаблоном при использовании Monitor.Wait. Это связано с тем, что пульсация потока не имеет семантического значения сама по себе. Это всего лишь сигнал о том, что состояние блокировки изменилось. Когда потоки просыпаются после блокировки на Wait, они должны перепроверить то же самое условие, которое изначально использовалось для блокировки потока, чтобы увидеть, может ли поток теперь действовать. Иногда он не может и поэтому должен блокировать еще несколько.

Лучшее эмпирическое правило заключается в том, что если есть сомнения в том, следует ли использовать проверку if или while, тогда всегда выбирайте цикл while, потому что это безопаснее. Фактически, я бы взял это до крайности и предлагаю всегда использовать цикл while, потому что нет никакого присущего преимущества в использовании более простой проверки if, и потому что проверка if почти всегда является неправильным выбором. Аналогичное правило выполняется для выбора, следует ли использовать Pulse или PulseAll. Если есть сомнения относительно того, какой из них использовать, всегда выбирайте PulseAll.

Ответ 2

вы должны продолжать проверять, остается ли очередь пустой или нет. Использование только в том случае, если бы только один раз проверить, подождать некоторое время, а затем удалить. Что, если в это время очередь все еще пуста? BANG! ошибка нижнего потока очереди...

Ответ 3

с условием , если, когда что-то выпустило блокировку queue.Count == 0 не будет проверяться снова и, возможно, ошибка нижнего потока очереди, поэтому мы должны проверить условие каждый время из-за concurrency, и это называется Spinning

Ответ 4

Почему в Unix это может пойти не так, из-за ложного пробуждения, возможности, вызванного сигналами ОС. Это побочный эффект, который не гарантируется никогда не случаться и на окнах. Это не наследие, так работает ОС. Если мониторы реализованы с точки зрения переменных условий, то есть.

def: ложное пробуждение - это перепланирование спящего потока на узле ожидания переменной состояния, который не был вызван действием, исходящим из текущих потоков программы (например, Pulse()).

Это неудобство может быть замаскировано на управляемых языках, например, очереди. Поэтому, прежде чем выйти из функции Wait(), структура может проверить, действительно ли этот запущенный поток действительно запрашивается для планирования, если он не находит себя в очереди выполнения, он может снова заснуть. Скрытие проблемы.

Ответ 5

if (queue.Count == 0)  

сделает.

Использование шаблона цикла для контекста ожидания и проверки состояния является, как мне кажется, остальным. Поскольку не-Windows, переменные монитора не .NET могут запускаться без фактического Pulse.

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