2009-10-13 2 views

ответ

48

Короткая версия:

lock(obj) {...} 

коротка рукой для Monitor.Enter/Monitor.Exit (с обработкой исключений и т.д.). Если никто не имеет блокировки, вы можете получить его (и запустить свой код), иначе ваш поток будет заблокирован до тех пор, пока блокировка не будет приобретена (другим потоком, освобождающим его).

Тупик обычно не происходит, когда либо A: две нити запирать вещи в различных порядках:

thread 1: lock(objA) { lock (objB) { ... } } 
thread 2: lock(objB) { lock (objA) { ... } } 

(здесь, если каждый из них приобретают первый замок, ни может никогда получить второе, так как ни один поток может выход для снятия их блокировки)

Этот сценарий можно свести к минимуму, всегда фиксируя его в том же порядке; и вы можете восстановить (в степени) с помощью Monitor.TryEnter (вместо Monitor.Enter/lock) и указав таймаут.

или B: вы можете заблокировать себя с вещами, как WinForms, когда нить переключение, удерживая блокировку:

lock(obj) { // on worker 
    this.Invoke((MethodInvoker) delegate { // switch to UI 
     lock(obj) { // oopsiee! 
      ... 
     } 
    }); 
} 

Тупик кажется очевидным выше, но это не так очевидно, когда у вас есть спагетти кода; Возможные ответы: не переключайте нить при удерживании блокировок или используйте BeginInvoke, чтобы вы могли хотя бы выйти из блокировки (позволяя играть в интерфейс).


Wait/Pulse/PulseAll различны; они предназначены для сигнализации.Я использую этот in this answer сигнализировать таким образом, чтобы:

  • Dequeue: если вы пытаетесь из очереди данных, когда очередь пуста, она ожидает другой поток, чтобы добавить данные, которые будят блокированный поток
  • Enqueue: если вы пытаетесь поставить в очередь данные, когда очередь заполнена, она ждет другого потока, чтобы удалить данные, которые будит заблокированное нить

Pulse только будит один нить - но я не достаточно башковитый, чтобы доказать что следующий поток i всегда я хочу, поэтому я предпочитаю использовать PulseAll и просто повторю проверку условий перед продолжением; В качестве примера:

 while (queue.Count >= maxSize) 
     { 
      Monitor.Wait(queue); 
     } 

С таким подходом, я могу смело добавить и другие значения из Pulse, без моего существующего кода при условии, что «я просыпался, поэтому есть данные» - что очень удобно, когда (в том же примере) Мне позже нужно было добавить метод Close().

1

Прочитано Jon Skeet's multi-part threading article.

Это действительно хорошо. Вы упомянули примерно треть пути.

+7

он даже не упоминает пульс или не подождет! Ссылка на статью John Skeet не дает ответ автоматически ... –

+2

О, действительно? Что это? 'http: // www.yoda.arachsys.com/csharp/threads/deadlocks.shtml' – Geo

+0

@Geo: да, этот подходит лучше;) –

7

Нет, они не защищают вас от тупиков. Это просто более гибкие инструменты для синхронизации потоков. Вот очень хорошее объяснение, как использовать их и очень важный образец использования - без этого шаблона вы сломаете все вещи: http://www.albahari.com/threading/part4.aspx

+1

ДЛЯ NOVICES ВЫСОКО РЕКОМЕНДУЕТ страницу albahari! Просматривает потоки и синхронизирует шаг за шагом с ясными примерами. – DanG

1

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

К сожалению, большая часть работы, необходимой для написания правильного многопоточного кода, в настоящее время является ответственностью разработчиков на C# (и многих других языках). Посмотрите, как F #, Haskell и Clojure обрабатывают это для совершенно другого подхода.

37

Простой рецепт использования Monitor.Wait и Monitor.Pulse. Она состоит из рабочих, босса, и телефона они используют для общения:

object phone = new object(); 

"Рабочая" Нить:

lock(phone) // Sort of "Turn the phone on while at work" 
{ 
    while(true) 
    { 
     Monitor.Wait(phone); // Wait for a signal from the boss 
     DoWork(); 
     Monitor.PulseAll(phone); // Signal boss we are done 
    } 
} 

A "Boss" резьба:

PrepareWork(); 
lock(phone) // Grab the phone when I have something ready for the worker 
{ 
    Monitor.PulseAll(phone); // Signal worker there is work to do 
    Monitor.Wait(phone); // Wait for the work to be done 
} 

Более сложные примеры следуют ...

«Работник с чем-то другим заниматься»:

lock(phone) 
{ 
    while(true) 
    { 
     if(Monitor.Wait(phone,1000)) // Wait for one second at most 
     { 
      DoWork(); 
      Monitor.PulseAll(phone); // Signal boss we are done 
     } 
     else 
      DoSomethingElse(); 
    } 
} 

"Нетерпеливый Boss":

PrepareWork(); 
lock(phone) 
{ 
    Monitor.PulseAll(phone); // Signal worker there is work to do 
    if(Monitor.Wait(phone,1000)) // Wait for one second at most 
     Console.Writeline("Good work!"); 
} 
+0

Я не понимаю. Как возможно, что Босс и Рабочий находятся в «телефонной» блокировке одновременно? – Marshall

+1

@Marshall Monitor.Wait выпускает блокировку «телефона» на следующую строку (предположительно на Boss). –

+0

@DennisGorelik ahh, я вижу. Я расширил ваш пункт [ниже] (http://stackoverflow.com/a/42581381/1282864) – jdpilgrim

1

К сожалению, ни из Wait(), Pulse() или PulseAll() имеют волшебное свойство, которое вы желающему за - что что используя этот API, вы автоматически избегаете взаимоблокировки.

Рассмотрим следующий код

object incomingMessages = new object(); //signal object 

LoopOnMessages() 
{ 
    lock(incomingMessages) 
    { 
     Monitor.Wait(incomingMessages); 
    } 
    if (canGrabMessage()) handleMessage(); 
    // loop 
} 

ReceiveMessagesAndSignalWaiters() 
{ 
    awaitMessages(); 
    copyMessagesToReadyArea(); 
    lock(incomingMessages) { 
     Monitor.PulseAll(incomingMessages); //or Monitor.Pulse 
    } 
    awaitReadyAreaHasFreeSpace(); 
} 

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

Почему?

В конце концов произойдет следующее:

  1. Всех потребительские потоки делают некоторую работу
  2. сообщений приходят, готовая площадь не может вместить больше никаких сообщений, и PulseAll() называется.
  3. Нет потребитель получает проснулся, потому что никто не ждет
  4. Все потребительские потоки называют Wait() [ТУПИК]

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

if (!canGrabMessage()) Monitor.Wait(incomingMessages); 

К сожалению, до сих пор не достаточно, чтобы исправить это. Чтобы исправить это также необходимость изменения стопорное сферы, где Monitor.PulseAll() называется:

LoopOnMessages() 
{ 
    lock(incomingMessages) 
    { 
     if (!canGrabMessage()) Monitor.Wait(incomingMessages); 
    } 
    if (canGrabMessage()) handleMessage(); 
    // loop 
} 

ReceiveMessagesAndSignalWaiters() 
{ 
    awaitMessagesArrive(); 
    lock(incomingMessages) 
    { 
     copyMessagesToReadyArea(); 
     Monitor.PulseAll(incomingMessages); //or Monitor.Pulse 
    } 
    awaitReadyAreaHasFreeSpace(); 
} 

Ключевым моментом является то, что в фиксированном коде, замки ограничивают возможные последовательности событий:

  1. а потребительские нити делает свою работу и петли

  2. Этот поток получает блокировку

    И благодаря блокировке в настоящее время верно, что либо:

  3. a. Сообщения еще не прибыли в готовой области, и он снимает блокировку с помощью вызова Wait() перед резьбой приемника сообщения может получить блокировку и скопировать несколько сообщений в готовую область, или

    б. Сообщения имеют уже прибыл в готовую область и получает сообщения INSTEAD OF вызова Wait(). (И в то время как он делает это решение невозможно для приемника сообщений, чтобы, например, получить блокировку и скопировать несколько сообщений в готовую область.)

В результате проблема исходного кода в настоящее время не происходит : 3. Когда PulseEvent() называется нет потребитель получает проснулся, потому что никто не ждет

Теперь заметим, что в этом коде вы должны получить фиксирующую сферу именно право. (Если, конечно, я получил это право!)

А также, так как вы должны использовать lock (или Monitor.Enter() и т.д.) для того, чтобы использовать Monitor.PulseAll() или Monitor.Wait() в тупиковой свободной моды, вам все равно придется беспокоиться о возможности другие взаимоблокировки, которые происходят из-за этого блокировки.

Итог: эти API, также легко завинтить и тупиковая с, т.е. довольно опасно

0

то, что общий бросил меня в том, что Pulse просто дает «головы», чтобы нить в Wait ,Нить ожидания не будет продолжаться до тех пор, пока нить, которая сделала Pulse, не выдает блокировку, и ожидающая нить успешно побеждает ее.

lock(phone) // Grab the phone 
{ 
    Monitor.PulseAll(phone); // Signal worker 
    Monitor.Wait(phone); // ****** The lock on phone has been given up! ****** 
} 

или

lock(phone) // Grab the phone when I have something ready for the worker 
{ 
    Monitor.PulseAll(phone); // Signal worker there is work to do 
    DoMoreWork(); 
} // ****** The lock on phone has been given up! ****** 

В обоих случаях это не не до «замок на телефоне было отказано», что другой поток может получить его.

Могут быть другие темы, ожидающие этого замка от Monitor.Wait(phone) или lock(phone). Только тот, кто выигрывает замок, будет продолжать.

Смежные вопросы