Не понимайте необходимость в Monitor.Pulse()
Согласно MSDN, Monitor.Wait()
:
Освобождает блокировку объекта и блокирует текущий поток, пока он повторно запирает замок.
Однако все, что я прочитал о Wait() и Pulse(), похоже, указывает на то, что простого освобождения блокировки на другом потоке недостаточно. Мне нужно сначала вызвать Pulse(), чтобы разбудить ожидающий поток.
Мой вопрос - почему? Темы, ожидающие блокировки на Monitor.Enter(), просто получают его при его выпуске. Нет необходимости "разбудить их". Кажется, он побеждает полезность Wait().
например.
static object _lock = new Object();
static void Main()
{
new Thread(Count).Start();
Sleep(10);
lock (_lock)
{
Console.WriteLine("Main thread grabbed lock");
Monitor.Pulse(_lock) //Why is this required when we're about to release the lock anyway?
}
}
static void Count()
{
lock (_lock)
{
int count = 0;
while(true)
{
Writeline("Count: " + count++);
//give other threads a chance every 10th iteration
if (count % 10 == 0)
Monitor.Wait(_lock);
}
}
}
Если я использую Exit() и Enter() вместо Wait(), я могу сделать:
static object _lock = new Object();
static void Main()
{
new Thread(Count).Start();
Sleep(10);
lock (_lock) Console.WriteLine("Main thread grabbed lock");
}
static void Count()
{
lock (_lock)
{
int count = 0;
while(true)
{
Writeline("Count: " + count++);
//give other threads a chance every 10th iteration
if (count % 10 == 0)
{
Monitor.Exit(_lock);
Monitor.Enter(_lock);
}
}
}
}
Ответы
Ответ 1
Вы используете Enter
/Exit
для получения эксклюзивного доступа к блокировке.
Вы используете Wait
/Pulse
, чтобы разрешить совместное уведомление: я хочу подождать, когда что-то произойдет, поэтому я ввожу блокировку и вызываю Wait
; уведомляющий код войдет в блокировку и вызовет Pulse
.
Две схемы связаны, но они не пытаются выполнить одно и то же.
Посмотрите, как бы вы реализовали очередь производителей/потребителей, где потребитель может сказать: "Разбудите меня, когда у вас есть предмет для меня, чтобы потреблять", без чего-то подобного.
Ответ 2
Прочитайте раздел Примечания на связанной странице MSDN:
Когда поток вызывает Wait, он освобождает блокировку объекта и входит в очередь ожидания объекта. Следующий поток в объектной готовой очереди (если он есть) получает блокировку и имеет эксклюзивное использование объекта. Все потоки, которые вызывают "Ожидание", остаются в очереди ожидания, пока не получат сигнал от Pulse или PulseAll, отправленный владельцем блокировки. Если Pulse отправлен, затрагивается только поток в начале очереди ожидания. Если PulseAll отправлен, все потоки, ожидающие этого объекта, будут затронуты. Когда сигнал принят, один или несколько потоков покидают очередь ожидания и входят в очередь готовности. Поток в готовой очереди разрешен для повторной блокировки.
Этот метод возвращает, когда вызывающий поток повторно блокирует объект. Обратите внимание, что этот метод блокируется бесконечно, если держатель блокировки не вызывает Pulse или PulseAll.
Итак, в основном, когда вы вызываете Monitor.Wait
, ваш поток находится в очереди ожидания. Чтобы повторно закрепить замок, он должен находиться в готовой очереди. Monitor.Pulse
перемещает первый поток в очереди ожидания в очередь готовности и, таким образом, позволяет повторно захватить блокировку.
Ответ 3
У меня было такое же сомнение, и, несмотря на некоторые интересные ответы (некоторые из них присутствуют здесь), я все еще продолжал искать более убедительный ответ.
Я думаю, что интересная и простая мысль по этому вопросу будет заключаться в следующем: я могу назвать Monitor.Wait(lockObj) в тот момент, когда ни один другой поток не ждет, чтобы получить блокировку на блокировкаObj объект. Я просто хочу дождаться, когда что-то случится (например, какое-то состояние объекта), и это то, что я знаю, что в конечном итоге произойдет в каком-то другом потоке. Как только это условие будет достигнуто, я хочу иметь возможность повторно зафиксировать блокировку, как только другой поток освободит свою блокировку.
По определению метода Monitor.Wait он освобождает блокировку и пытается ее снова получить. Если он не дождался вызова метода Monitor.Pulse, прежде чем пытаться снова получить блокировку, он просто освободит блокировку и немедленно ее снова получит (в зависимости от вашего кода, возможно, в цикле).
То есть, я думаю, что это интересно, пытаясь понять необходимость метода Monitor.Pulse, взглянув на его полезность в функционировании метода Monitor.Wait.
Подумайте так: "Я не хочу выпускать эту блокировку и сразу же пытаюсь ее снова приобрести, потому что я НЕ ХОЧУ быть МЕНЯ, чтобы следующий поток приобрел эту блокировку. И я также не хочу оставаться в цикле, содержащем вызов Thread.Sleep проверки какого-либо флага или чего-то еще, чтобы узнать, когда условие, в котором я жду, было достигнуто, чтобы я мог попытаться восстановить блокировку. хотите" спячки "и автоматически просыпаться, как только кто-то говорит мне, что условие, которого я жду, было достигнуто".