Монітор проти блокування


88

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

РЕДАГУВАТИ: З відповідей на сьогоднішній день видно, що 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-konačno для нетривіальних процедур.
kizzx2

5
Відновлення спільного стану IMO є ортогональним блокуванню / багатопотоковості. Тож це слід робити за допомогою спроби-лову / нарешті всередині lockблоку.
CodesInChaos

2
@ kizzx2: Такий шаблон був би особливо приємний із замками для читачів та авторів. Якщо в коді, який утримує блокування зчитувача, трапляється виняток, немає жодних причин очікувати, що захищений ресурс може бути пошкоджений, а отже, немає причини його анулювати. Якщо виняток трапляється всередині блокування запису, і код обробки винятків прямо не вказує на те, що стан об’єкта, що охороняється, було відремонтовано, це може припустити, що об’єкт може бути пошкоджений і повинен бути визнаний недійсним. ІМХО, несподівані винятки не повинні призводити до збою програми, але повинні призводити до недійсності всього, що може бути пошкодженим.
supercat

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);
}

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є незмінними, тоді ви викликаєте його з несинхронізованого блоку коду .. але в будь-якому випадку. Це просто цікавий факт.


Msgstr "Це тому, що Monitor.Exit (lockObject); діятиме на lockObject". Тоді замок нічого не робить з об’єктом? Як працює замок?
Юго

@YugoAmaryl, я вважаю, це тому, що оператор блокування пам’ятає спочатку передане посилання, а потім використовує його замість того, щоб використовувати змінене посилання, наприклад:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Журавлев А.

3

Обидва - це одне і те ж. lock є c різким ключовим словом і використовуйте клас 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

3

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

Блокування є ярликом, і це опція для основного використання.

Якщо вам потрібно більше контролю, монітор - кращий варіант. Ви можете використовувати Wait, TryEnter та Pulse для вдосконаленого використання (наприклад, бар’єри, семафори тощо).


1

Замок Ключове слово Lock гарантує, що один потік виконує фрагмент коду одночасно.

замок (lockObject)

        {
        //   Body
        }

Ключове слово lock позначає блок оператора як критичний розділ, отримуючи блокування взаємного виключення для даного об'єкта, виконуючи оператор, а потім звільняючи блокування

Якщо інший потік намагається ввести заблокований код, він буде чекати, блокувати, поки об'єкт не буде звільнений.

Монітор Монітор є статичним класом і належить до простору імен System.Threading.

Він забезпечує ексклюзивний замок на об'єкті, так що лише один потік може увійти в критичну секцію в будь-який момент часу.

Різниця між монітором і блокуванням в C #

Замок - це ярлик для Monitor.Enter with try і нарешті. Блокування ручки спробувати і нарешті заблокувати внутрішньо Заблокувати = Монітор + спробувати остаточно.

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

C # Monitor.wait(): Потік чекає сповіщення інших потоків.

Monitor.pulse(): Потік повідомляє інший потік.

Monitor.pulseAll(): Потік повідомляє всі інші потоки в процесі


0

На додаток до всіх наведених вище пояснень, lock - це оператор C #, тоді як Monitor - це клас .NET, розташований у просторі імен System.Threading.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.