Монитор против блокировки

89

Когда в C # целесообразно использовать Monitorкласс или lockключевое слово для обеспечения безопасности потоков?

РЕДАКТИРОВАТЬ: Судя по ответам до сих пор, lockэто короткая рука для серии вызовов Monitorкласса. Для чего именно нужен вызов блокировки? Или, точнее,

class LockVsMonitor
{
    private readonly object LockObject = new object();
    public void DoThreadSafeSomethingWithLock(Action action)
    {
        lock (LockObject)
        {
            action.Invoke();
        }
    }
    public void DoThreadSafeSomethingWithMonitor(Action action)
    {
        // What goes here ?
    }
}

Обновить

Спасибо всем за вашу помощь: я опубликовал еще один вопрос в продолжение некоторой информации, которую вы все предоставили. Поскольку вы, кажется, хорошо разбираетесь в этой области, я разместил ссылку: Что плохого в этом решении для блокировки и управления заблокированными исключениями?

умный пещерный человек
источник

Ответы:

89

Эрик Липперт говорит об этом в своем блоге: « Блокировки и исключения несовместимы.

Эквивалентный код отличается в C # 4.0 и более ранних версиях.


В C # 4.0 это:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    { body }
}
finally
{
    if (lockWasTaken) Monitor.Exit(temp);
}

Он полагается на Monitor.Enterатомарную установку флага при взятии блокировки.


А раньше было:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}

Это зависит от того, не возникает ли исключение между Monitor.Enterи try. Я думаю, что в отладочном коде это условие было нарушено, потому что компилятор вставил NOP между ними и, таким образом, сделал прерывание потока между этими возможными.

Коды
источник
Как я уже говорил, первый пример - это C # 4, а второй - то, что используется в более ранних версиях.
CodesInChaos
В качестве побочного примечания, C # через CLR упоминает предупреждение ключевого слова lock: вам часто может потребоваться что-то сделать для восстановления поврежденного состояния (если применимо) перед снятием блокировки. Поскольку ключевое слово lock не позволяет нам помещать вещи в блок catch, нам следует подумать о написании длинной версии try-catch-finally для нетривиальных подпрограмм.
kizzx2
5
IMO восстановление общего состояния ортогонально блокировке / многопоточности. Так что это нужно делать с помощью try-catch / finally внутри lockблока.
CodesInChaos,
2
@ kizzx2: Такой шаблон был бы особенно хорош для блокировок чтения-записи. Если исключение возникает в коде, который содержит блокировку считывателя, нет причин ожидать, что защищенный ресурс может быть поврежден, и, следовательно, нет причин делать его недействительным. Если исключение возникает в блокировке записи и код обработки исключений явно не указывает, что состояние охраняемого объекта было восстановлено, это может указывать на то, что объект может быть поврежден и должен быть признан недействительным. IMHO, неожиданные исключения не должны приводить к сбою программы, но должны аннулировать все, что может быть повреждено.
supercat 03
2
@ArsenZahray Вам не нужна Pulseпростая блокировка. Это важно в некоторых сложных сценариях многопоточности. Я никогда не использовал Pulseнапрямую.
CodesInChaos
43

lockэто просто ярлык Monitor.Enterс try+ finallyи Monitor.Exit. Используйте оператор блокировки всякий раз, когда этого достаточно - если вам нужно что-то вроде TryEnter, вам придется использовать Monitor.

Лукаш Новотны
источник
23

Оператор блокировки эквивалентен:

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Однако имейте в виду, что Monitor может также Wait () и Pulse () , которые часто полезны в сложных многопоточных ситуациях.

Обновить

Однако в C # 4 это реализовано иначе:

bool lockWasTaken = false;
var temp = obj;
try 
{
     Monitor.Enter(temp, ref lockWasTaken); 
     //your code
}
finally 
{ 
     if (lockWasTaken) 
             Monitor.Exit(temp); 
} 

Спасибо CodeInChaos за комментарии и ссылки

Шехар_Про
источник
В C # 4 оператор блокировки реализован иначе. blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
CodesInChaos
14

Monitorболее гибкий. Мой любимый вариант использования монитора - это когда вы не хотите ждать своей очереди и просто пропустите :

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}
Alex
источник
6

Как говорили другие, lock"эквивалентно"

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Но просто из любопытства lockсохранит первую ссылку, которую вы ему передали, и не будет выбрасывать, если вы ее измените. Я знаю, что не рекомендуется менять заблокированный объект, и вы не хотите этого делать.

Но опять же, для науки это работает нормально:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        lock (lockObject)
        {
            lockObject += "x";
        }
    }));
Task.WaitAll(tasks.ToArray());

... А этого нет:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        Monitor.Enter(lockObject);
        try
        {
            lockObject += "x";
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }));
Task.WaitAll(tasks.ToArray());

Ошибка:

Исключение типа System.Threading.SynchronizationLockException произошло в 70783sTUDIES.exe, но не было обработано в пользовательском коде.

Дополнительная информация: Метод синхронизации объектов был вызван из несинхронизированного блока кода.

Это потому, что Monitor.Exit(lockObject);будет действовать то, lockObjectчто было изменено, потому что stringsони неизменяемы, тогда вы вызываете его из несинхронизированного блока кода ... но в любом случае. Это просто забавный факт.

Андре Пена
источник
«Это потому, что Monitor.Exit (lockObject); будет действовать на lockObject». Тогда блокировка ничего не делает с объектом? Как работает блокировка?
Юго Амарил
@YugoAmaryl, я полагаю, это потому, что оператор блокировки учитывает сначала переданную ссылку, а затем ее использование вместо использования измененной ссылки, например:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Журавлев А.
3

Оба - одно и то же. lock - ключевое слово c sharp и использовать класс Monitor.

http://msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx

РобертоБр
источник
3
Посмотрите на msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx «Фактически, ключевое слово блокировки реализовано с помощью класса Monitor. Например»
RobertoBr
1
базовая реализация блокировки использует Monitor, но это не одно и то же, рассмотрите методы, предоставляемые монитором, которые не существуют для блокировки, и способ блокировки и разблокировки в отдельных блоках кода.
eran otzap 03
3

Блокировка и основное поведение монитора (ввод + выход) более или менее одинаковы, но у монитора есть больше опций, которые позволяют вам больше возможностей синхронизации.

Блокировка - это ярлык, и это вариант для базового использования.

Если вам нужно больше контроля, монитор - лучший вариант. Вы можете использовать Wait, TryEnter и Pulse для расширенного использования (например, барьеры, семафоры и т. Д.).

Борха
источник
1

Lock Ключевое слово Lock гарантирует, что один поток одновременно выполняет часть кода.

блокировка (lockObject)

        {
        //   Body
        }

Ключевое слово lock помечает блок инструкций как критическую секцию, получая блокировку взаимного исключения для данного объекта, выполняя инструкцию и затем снимая блокировку.

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

Монитор Монитор - это статический класс, принадлежащий пространству имен System.Threading.

Он обеспечивает исключительную блокировку объекта, так что только один поток может войти в критическую секцию в любой момент времени.

Разница между монитором и блокировкой в ​​C #

Блокировка - это ярлык для Monitor.Enter с помощью try и finally. Блокировать дескрипторы пытаются и, наконец, заблокировать внутренне Lock = Monitor + try finally.

Если вы хотите больше контроля , чтобы реализовать передовую многопоточность решения с использованием TryEnter() Wait(), Pulse()и PulseAll()метод, то класс Monitor вашего вариант.

C # Monitor.wait(): поток ожидает уведомления других потоков.

Monitor.pulse(): Поток уведомляет другой поток.

Monitor.pulseAll(): Поток уведомляет все другие потоки в процессе

Атри
источник
0

В дополнение ко всем приведенным выше объяснениям, lock - это оператор C #, тогда как Monitor - это класс .NET, расположенный в пространстве имен System.Threading.

PureSilence
источник