2009-08-31 12 views
28

byłem pod wrażeniem po przeczytaniu this article że lepiej jest użyć Monitora/lock do synchronizacji wątków, ponieważ nie korzysta z rodzimych zasobówmonitora vs WaitHandle oparciu synchronizacji wątek

Specific cytat (od strony 5 tego artykułu):

Monitora/Impuls nie jest jedynym sposobem czekania na coś się stało w jednym wątku i mówienie tego wątku, że stało się to w innym. Programiści Win32 od dłuższego czasu stosują różne inne mechanizmy i są one ujawniane przez klasy AutoResetEvent, ManualResetEvent i Mutex, które pochodzą z WaitHandle. Wszystkie te klasy znajdują się w Przestrzeni nazw System.Threading. (Mechanizm Win32 Semaphore nie ma zarządzanego opakowania w .NET 1.1. Jest obecny w .NET 2.0, ale jeśli musisz go użyć wcześniej, możesz go zawinąć samodzielnie za pomocą P/Invoke lub napisać swój własny semafor klasa.)

Niektóre osoby mogą być zaskoczone tym, że używanie tych klas może być znacznie wolniejsze niż używanie różnych metod monitorowania. Wydaje mi się, że dzieje się tak dlatego, że "wychodzenie" z kodu zarządzanego do natywnych wywołań Win32 i powrót do "znowu" jest drogi w porównaniu z całkowicie zarządzanym widokiem rzeczy, które zapewnia Monitor. Czytelnik wyjaśnił również, że monitory są implementowane w trybie użytkownika, podczas gdy użycie uchwytów oczekiwania wymaga przełączenia w tryb jądra, co jest dość kosztowne.

Ale odkąd odkryłem SO i przeczytałem kilka pytań/odpowiedzi tutaj zacząłem wątpić w moje rozumienie kiedy użyć każdego. Wydaje się, że wiele osób zaleca używanie Auto/ManualResetEvent w przypadkach, w których działałby Monitor.Wait/Pulse. Czy ktoś może mi wyjaśnić, kiedy synchronizacja oparta na WaitHandle powinna być używana przez Monitor?

Dzięki

Odpowiedz

51

Problem z Monitor.Pulse/Wait jest to, że sygnał może się zgubić.

Na przykład:

var signal = new ManualResetEvent(false); 

// Thread 1 
signal.WaitOne(); 

// Thread 2 
signal.Set(); 

To zawsze będzie działać bez względu na to, w którym dwie wypowiedzi w różnych wątkach są wykonywane. To także bardzo czysta abstrakcja i bardzo jasno wyraża twoją intencję.

teraz rzucić okiem na ten sam przykład przy użyciu monitora:

var signal = new object(); 

// Thread 1 
lock (signal) 
{ 
    Monitor.Wait(signal); 
} 

// Thread 2 
lock (signal) 
{ 
    Monitor.Pulse(signal); 
} 

Tu sygnał (Pulse) zgubisz jeśli Pulse jest wykonywany przed Wait.

Aby rozwiązać ten problem, trzeba coś takiego:

var signal = new object(); 
var signalSet = false; 

// Thread 1 
lock (signal) 
{ 
    while (!signalSet) 
    { 
     Monitor.Wait(signal); 
    } 
} 

// Thread 2 
lock (signal) 
{ 
    signalSet = true; 
    Monitor.Pulse(signal); 
} 

To działa i to chyba nawet bardziej wydajnych i lekki, ale jest mniej czytelny. I właśnie tam zaczyna się ból głowy zwany współbieżnością.

  • Czy ten kod naprawdę działa?
  • W każdym rogu przypadku?
  • Posiada więcej niż dwa wątki? (Podpowiedź: nie)
  • Jak to przetestować?

Solidna, niezawodna, czytelna abstrakcja jest często lepsza od surowej wydajności.

Dodatkowo WaitHandles zapewnić jakieś fajne rzeczy jak czekanie na zestaw uchwytów do ustawienia itp Realizacja tego z monitorów sprawia, że ​​ból głowy nawet gorzej ...


zasada:

  • Zastosowanie Monitory (lock) w celu zapewnienia wyłącznego dostępu do współdzielonych zasobów
  • Korzystanie w aitHandles (Manual/AutoResetEvent/Semafor) do wysyłania sygnałów pomiędzy wątkami
+0

bardzo dobre wyjaśnienie. –

+0

Dzięki za wyjaśnienie, to ma sens. – Matt

+0

Uwielbiam to miejsce ... możesz łatwo znaleźć i odpowiedzieć w ten sposób, co pozwoli Ci zaoszczędzić godziny poszukiwań dzięki książkom i artykułom ... –

3

myślę, że mam całkiem dobry przykład 3 pocisku (a gdy ten wątek jest nieco stary, to może komuś pomóc).

Mam trochę kodu, w którym wątek A odbiera komunikaty sieciowe, wpisuje je, a następnie pulsuje wątek B. Wątek B blokuje, odejmuje wszystkie wiadomości, odblokowuje kolejkę, a następnie przetwarza wiadomości.

Pojawia się problem polegający na tym, że wątek B przetwarza i nie czeka, jeśli A otrzyma nowy komunikat sieciowy, zgina się i pulsuje ... Cóż, B nie czeka, aby puls właśnie wyparował. Jeśli B zakończy działanie i trafi do Monitor.Wait(), to ostatnio dodana wiadomość będzie się kręcić, dopóki nie pojawi się inny komunikat i otrzymamy impuls.

Zauważ, że ten problem nie bardzo powierzchniowych na chwilę, jak pierwotnie moja całej pętli było coś takiego:

while (keepgoing) 
    { 
    lock (messageQueue) 
     { 
     while (messageQueue.Count > 0) 
      ProcessMessages(messageQueue.DeQueue()); 

     Monitor.Wait(messageQueue); 
     } 
    } 

Ten problem nie pojawiają się (dobrze, były rzadkie osobliwości przy zamykaniu systemu, więc byłem trochę podejrzliwy wobec tego kodu), dopóki nie zdecydowałem, że przetwarzanie wiadomości (potencjalnie długo działające) nie powinno blokować kolejki, ponieważ nie było ku temu powodu. Więc zmieniłem go, aby usunąć kolejność wiadomości, zostawić blokadę, TO przetwarzanie. A potem wydawało mi się, że zacząłem brakować wiadomości, albo przybędą dopiero po drugim wydarzeniu ...

-1

dla przypadku @Willa Gore'a, dobrą praktyką jest ciągłe przetwarzanie kolejki, dopóki nie będzie puste, przed dzwoniąc do Monitor.Wait. Np .:

while (keepgoing) 
{ 
    List<Message> nextMsgs = new List<Message>(); 
    lock (messageQueue) 
    { 
    while (messageQueue.Count == 0) 
    { 
     try 
     { 
      Monitor.Wait(messageQueue); 
     } 
     catch(ThreadInterruptedException) 
     { 
      //... 
     } 
    } 
    while (messageQueue.Count > 0) 
     nextMsgs.Add(messageQueue.DeQueue()); 
    } 
    if(nextMsgs.Count > 0) 
    ProcessMessages(nextMsgs); 
} 

powinno to rozwiązać zarówno napotkany problem, jak i skrócić czas blokowania (bardzo ważne!).

+0

Zmienna 'nextMsg' jest zadeklarowana, ale nigdy nie przypisana. 'message' nie jest zadeklarowane, ale jest przypisane. Jeśli dwie zmienne mają być takie same, przetwarzana będzie tylko * ostatnia * wiadomość - reszta zostanie odrzucona (dość prosty błąd logiczny). 'Monitor.Wait' jest używany poza zasięgiem' lock', potencjalnie blokując kolejkę na zawsze. To jest dość złe. –

+0

Dzięki Kirill Shlenskiy za wskazanie błędów i przykro z powodu tych błędów. Juz naprawione. Próbowałem pokazać, jak rozwiązać 2 problemy z poprzedniego postu, ale nie zrobiłem tego zbyt ostrożnie. Kiedy Monitor.Wait jest używany poza blokadą, to faktycznie rzuci wyjątek SynchronizationLockException. – Jim