Monitor a blokada

89

Kiedy jest właściwe użycie Monitorklasy lub locksłowa kluczowego dla bezpieczeństwa wątków w C #?

EDYCJA: Z dotychczasowych odpowiedzi wynika, że lockjest to krótka ręka na serię wezwań do Monitorklasy. Do czego dokładnie służy krótka ręka blokady? Albo bardziej wyraźnie,

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 ?
    }
}

Aktualizacja

Dziękuję wszystkim za pomoc: wysłałem kolejne pytanie w związku z niektórymi podanymi przez was informacjami. Ponieważ wydaje się, że jesteś dobrze zorientowany w tej dziedzinie, opublikowałem link: Co jest złego w tym rozwiązaniu do blokowania i zarządzania zablokowanymi wyjątkami?

smartcaveman
źródło

Odpowiedzi:

89

Eric Lippert mówi o tym na swoim blogu: Blokady i wyjątki nie idą w parze

Odpowiedni kod różni się między C # 4,0 i wcześniejszymi wersjami.


W C # 4.0 jest to:

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

Polega na Monitor.Enteratomowym ustawianiu flagi, kiedy blokada jest zajęta.


A wcześniej było:

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

Opiera się to na tym, że żaden wyjątek nie jest zgłaszany między Monitor.Entera try. Myślę, że w kodzie debugowania ten warunek został naruszony, ponieważ kompilator wstawił między nimi NOP, a tym samym umożliwił przerwanie wątku między nimi.

CodesInChaos
źródło
Jak już wspomniałem, pierwszym przykładem jest C # 4, a drugim jest to, czego używają wcześniejsze wersje.
CodesInChaos
Na marginesie, C # za pośrednictwem środowiska CLR wspomina o zastrzeżeniu słowa kluczowego lock: często możesz chcieć coś zrobić, aby przywrócić uszkodzony stan (jeśli dotyczy) przed zwolnieniem blokady. Ponieważ słowo kluczowe lock nie pozwala nam umieścić rzeczy w bloku catch, powinniśmy rozważyć napisanie długiej wersji try-catch-last dla nietrywialnych procedur.
kizzx2
5
Przywracanie stanu udostępnionego przez IMO jest ortogonalne do blokowania / wielowątkowości. Dlatego należy to zrobić za pomocą try-catch / ostatecznie wewnątrz lockbloku.
CodesInChaos
2
@ kizzx2: Taki wzorzec byłby szczególnie przyjemny w przypadku blokad czytelnika-pisarza. Jeśli wyjątek wystąpi w kodzie, który utrzymuje blokadę czytnika, nie ma powodu, aby oczekiwać, że chroniony zasób może zostać uszkodzony, a zatem nie ma powodu, aby go unieważnić. Jeśli wyjątek wystąpi w ramach blokady zapisującej, a kod obsługi wyjątków nie wskazuje wyraźnie, że stan chronionego obiektu został naprawiony, sugerowałoby to, że obiekt może być uszkodzony i powinien zostać unieważniony. IMHO, nieoczekiwane wyjątki nie powinny powodować awarii programu, ale powinny unieważniać wszystko, co może być uszkodzone.
supercat
2
@ArsenZahray Nie potrzebujesz Pulseprostego blokowania. Jest to ważne w niektórych zaawansowanych scenariuszach wielowątkowości. Nigdy nie korzystałem Pulsebezpośrednio.
CodesInChaos
43

lockjest po prostu skrótem do Monitor.Enterz try+ finallyi Monitor.Exit. Używaj instrukcji lock, kiedy tylko jest to wystarczające - jeśli potrzebujesz czegoś takiego jak TryEnter, będziesz musiał użyć Monitora.

Lukáš Novotný
źródło
23

Instrukcja lock jest równoważna z:

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

Należy jednak pamiętać, że Monitor może również Wait () i Pulse () , które są często przydatne w złożonych sytuacjach wielowątkowych.

Aktualizacja

Jednak w C # 4 jest inaczej zaimplementowany:

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

Podziękowania dla CodeInChaos za komentarze i linki

Shekhar_Pro
źródło
W C # 4 instrukcja lock jest implementowana inaczej. blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
CodesInChaos
14

Monitorjest bardziej elastyczny. Moim ulubionym przypadkiem użycia monitora jest sytuacja, gdy nie chcesz czekać na swoją kolej i po prostu pomiń :

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}
Alex
źródło
6

Jak powiedzieli inni, lockjest „równoważne” z

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

Ale tylko z ciekawości, lockzachowa pierwsze przekazane do niego odniesienie i nie wyrzuci, jeśli je zmienisz. Wiem, że nie zaleca się zmiany zablokowanego obiektu i nie chcesz tego robić.

Ale znowu, dla nauki, to działa dobrze:

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

... a to nie oznacza:

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

Błąd:

Wyjątek typu „System.Threading.SynchronizationLockException” wystąpił w 70783sTUDIES.exe, ale nie został obsłużony w kodzie użytkownika

Informacje dodatkowe: Metoda synchronizacji obiektów została wywołana z niezsynchronizowanego bloku kodu.

Dzieje się tak, ponieważ Monitor.Exit(lockObject);będzie działać zgodnie z lockObjecttym, co się zmieniło, ponieważ stringssą niezmienne, wtedy wywołujesz go z niezsynchronizowanego bloku kodu ... ale tak czy inaczej. To po prostu zabawny fakt.

André Pena
źródło
„Dzieje się tak, ponieważ Monitor.Exit (lockObject); będzie działać na lockObject”. Więc zamek nic nie robi z przedmiotem? Jak działa zamek?
Yugo Amaryl
@YugoAmaryl, przypuszczam, że dzieje się tak dlatego, że instrukcja lock pamięta najpierw przekazaną referencję, a następnie używa jej zamiast zmienionej referencji, na przykład:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.
3

Obie są tym samym. lock jest słowem kluczowym cisre i użyj klasy Monitor.

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

RobertoBr
źródło
3
Spójrz na msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx "W rzeczywistości słowo kluczowe lock jest zaimplementowane w klasie Monitor. Na przykład"
RobertoBr
1
podstawowa implementacja blokady wykorzystuje Monitor, ale to nie to samo, rozważ metody dostarczane przez monitor, które nie istnieją dla blokady, oraz sposób, w jaki można blokować i odblokowywać w oddzielnych blokach kodu.
eran otzap
3

Blokada i podstawowe zachowanie monitora (wejście + wyjście) są mniej więcej takie same, ale monitor ma więcej opcji, które pozwalają na większe możliwości synchronizacji.

Blokada jest skrótem i jest opcją do podstawowego zastosowania.

Jeśli potrzebujesz większej kontroli, monitor jest lepszą opcją. Możesz użyć Wait, TryEnter i Pulse, do zaawansowanych zastosowań (takich jak bariery, semafory i tak dalej).

Borja
źródło
1

Słowo kluczowe Lock Lock zapewnia, że ​​jeden wątek wykonuje jednocześnie fragment kodu.

lock (lockObject)

        {
        //   Body
        }

Słowo kluczowe lock oznacza blok instrukcji jako sekcję krytyczną poprzez uzyskanie blokady wzajemnego wykluczania dla danego obiektu, wykonanie instrukcji, a następnie zwolnienie blokady

Jeśli inny wątek spróbuje wprowadzić zablokowany kod, będzie czekał, blokuje się, aż obiekt zostanie zwolniony.

Monitor Monitor jest klasą statyczną i należy do przestrzeni nazw System.Threading.

Zapewnia wyłączną blokadę obiektu, dzięki czemu tylko jeden wątek może wejść do krytycznej sekcji w danym momencie.

Różnica między monitorem a blokadą w języku C #

Blokada jest skrótem do Monitora, wprowadź spróbuj i na koniec. Zablokuj uchwyty spróbuj i ostatecznie zablokuj wewnętrznie Zablokuj = Monitor + spróbuj w końcu.

Jeśli chcesz mieć większą kontrolę w celu wdrożenia zaawansowanych rozwiązań wielowątkowymi użyciu TryEnter() Wait(), Pulse()oraz PulseAll()metod, a następnie klasa Monitor jest twój wybór.

C # Monitor.wait(): Wątek czeka na powiadomienie innych wątków.

Monitor.pulse(): Wątek powiadamia inny wątek.

Monitor.pulseAll(): Wątek powiadamia wszystkie inne wątki w procesie

Aatrey
źródło
0

Oprócz wszystkich powyższych wyjaśnień, lock jest instrukcją C #, podczas gdy Monitor to klasa .NET znajdująca się w przestrzeni nazw System.Threading.

PureSilence
źródło