.NET Threading - объект синхронизации - PullRequest
2 голосов
/ 17 июня 2011

У меня есть многопоточное приложение.

Один поток вставляется в очередь, и многие чтения потока формируют эту очередь.Для правильного чтения потоки считывателя блокируют очередь, как показано в следующем коде.

Мой вопрос: блокируется ли поток inserter , когда следующий код вызывается потоками считывателя, так как он используетта же очередь?Или продолжает вставлять без перерыва?

lock ( MsgQueue ) {
    if ( MsgQueue.Count == 0 ) {
         Monitor.Wait( MsgQueue );
         continue;
    }
    msg = MsgQueue.Dequeue( );
}

Ответы [ 5 ]

2 голосов
/ 17 июня 2011

Да, производитель (или инсертор) будет заблокирован, пока блокировка удерживается потребителем.Обратите внимание, что блокировка снимается вызовом Monitor.Wait и затем повторно запрашивается, когда поток управления возвращается обратно к вызывающей стороне.Все это предполагает, что ваш производитель пытается получить такую ​​же блокировку.

Как примечание: способ кодирования потребителя несколько менее эффективен, чем мог бы быть.Поскольку у вас есть оператор continue, я должен предположить, что цикл while оборачивает lock, что, вероятно, делает ваш код более похожим на следующее.

object msg = null;
while (msg == null)
{
  lock (MsgQueue) 
  {
    if (MsgQueue.Count == 0) 
    {
      Monitor.Wait(MsgQueue);
      continue;
    }
    msg = MsgQueue.Dequeue();
  }
}

Это можно изменить, чтобыусловие ожидания перепроверяется внутри блока lock.Таким образом, вам не нужно снимать и повторно захватывать блокировку для выполнения проверки.

object msg = null;
lock (MsgQueue) 
{
  while (MsgQueue.Count == 0) 
  {
    Monitor.Wait(MsgQueue);
  }
  msg = MsgQueue.Dequeue();
}

Опять же, поскольку я вижу оператор continue, я предполагаю, что вы знаете, что условие ожидания всегда должно быть перепроверенопосле Wait.Но на случай, если вы не знаете об этом требовании, я изложу его здесь, потому что оно важно.

Если условие ожидания не перепроверено и имеется 2 или более потребителей, то один из них может попасть внутрьзаблокировать и снять с очереди последний элемент.Это все еще может произойти, даже если другой потребитель был перемещен из очереди ожидания в очередь готовности посредством вызова на номер Pulse или PulseAll, но у него не было возможности повторно получить блокировку перед первым потребителем.Очевидно, что без повторной проверки потребитель может попытаться работать в пустой очереди.Не имеет значения, используется ли Pulse или PulseAll на стороне производства.Проблема все еще существует, потому что Monitor не дает предпочтения Wait выше Enter.

Обновление:

Я забыл указатьчто если вы используете .NET 4.0, вы можете воспользоваться BlockingCollection , который является реализацией очереди блокировки.Это безопасно для нескольких производителей и потребителей и делает всю блокировку за вас, если очередь пуста.

2 голосов
/ 17 июня 2011

Другой поток будет заблокирован блокировкой (MsgQueue), пока этот поток находится в lock , но не в Monitor.Wait (который снимает блокировку, чтобы другие потоки могли Pulse).

Это шаблон условной переменной: удерживайте блокировку во время работы с общим состоянием (экземпляр очереди), но снимите ее, ожидая изменения условия (Monitor.Wait).

Обновление: на основе комментария:

Нет, оно просто вставляется.Блокировка для средства вставки отсутствует

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

Update # 2 : если эта очередь в основном работаетиспользуется для передачи объектов из одного набора (исходных) потоков в другой набор (рабочих) потоков (где каждый набор может быть просто одним), тогда вы должны рассмотреть ConcurrentQueue, который является потокобезопасным (хотя и выпотребуется что-то вроде события , чтобы указать, что в очереди что-то есть, чтобы избежать опроса работников).

1 голос
/ 17 июня 2011

Нет. Я думаю, что ваш вопрос о значении lock ( MsgQueue ), и метафора может быть немного вводящей в заблуждение. Блокировка для объекта никоим образом не изменяет состояние этого объекта и не блокирует другие потоки, если только эти потоки не используют lock для того же объекта.

Вот почему вы часто видите этот (лучший) шаблон:

private Queue<MyClass> _queue = ...;
private object _queueLock = new object();
...
lock(_queueLock )
{
    _queue.Enqueue(item);
}

Ссылка, используемая в замке, служит только «билетом».

1 голос
/ 17 июня 2011

Резьба вставки в точках блокируется, да.

    lock ( MsgQueue ) {
        if ( MsgQueue.Count == 0 ) {  // LINE 1
            Monitor.Wait( MsgQueue ); // LINE 2
            continue;
        }
        msg = MsgQueue.Dequeue( ); // LINE 3
    }

В строке 1 считыватель удерживает блокировку, поэтому устройство вставки блокируется.

В строке 2 блокировка снимается и не восстанавливается до тех пор, пока инсертор предположительно не вызовет Monintor.Pulse on MsgQueue.

В строке 3 блокировка все еще удерживается (из строки 1), а затем она снова разблокируется из-за выхода из области действия lock.

1 голос
/ 17 июня 2011

Если поток вставки вызывает lock ( MsgQueue ), то, очевидно, он будет блокироваться всякий раз, когда один из читателей заблокировал очередь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...