Jakie są różnice między różnymi opcjami synchronizacji wątków w C #?

164

Czy ktoś może wyjaśnić różnicę między:

  • lock (someobject) {}
  • Korzystanie z Mutex
  • Korzystanie z semafora
  • Korzystanie z monitora
  • Korzystanie z innych klas synchronizacji .Net

Po prostu nie mogę tego rozgryźć. Wydaje mi się, że pierwsze dwa są takie same?

user38834
źródło
Ten link bardzo mi pomógł: albahari.com/threading
Raphael

Odpowiedzi:

135

Świetne pytanie. Może się mylę… Spróbuję… Wersja nr 2 mojej pierwotnej odpowiedzi… z odrobiną większego zrozumienia. Dzięki za zmuszenie mnie do czytania :)

zamek (obj)

  • jest konstrukcją CLR, która służy do synchronizacji wątków (wewnątrz obiektu?). Zapewnia, że ​​tylko jeden wątek może przejąć na własność blokadę obiektu i wprowadzić zablokowany blok kodu. Inne wątki muszą czekać, aż aktualny właściciel zwolni blokadę, wychodząc z bloku kodu. Zaleca się również zablokowanie prywatnego obiektu członkowskiego swojej klasy.

Monitory

  • lock (obj) jest implementowany wewnętrznie przy użyciu monitora. Powinieneś preferować lock (obj), ponieważ zapobiega to głupstwom, takim jak zapominanie o procedurze czyszczenia. Jeśli wolisz, konstrukcja Monitora jest idiotyczna.
    Korzystanie z programu Monitor jest generalnie preferowane zamiast muteksów, ponieważ monitory zostały zaprojektowane specjalnie dla platformy .NET Framework i dzięki temu lepiej wykorzystują zasoby.

Korzystanie z blokady lub monitora jest przydatne do zapobiegania równoczesnemu wykonywaniu bloków kodu wrażliwych na wątki, ale te konstrukcje nie pozwalają jednemu wątkowi na komunikowanie zdarzenia z innym. Wymaga to zdarzeń synchronizacji , które są obiektami, które mają jeden z dwóch stanów, sygnalizowanym i nie sygnalizowanym, których można używać do aktywowania i zawieszania wątków. Mutex, semafory to koncepcje na poziomie systemu operacyjnego. np. z nazwanym muteksem można synchronizować wiele (zarządzanych) exe (upewniając się, że tylko jedno wystąpienie aplikacji działa na komputerze).

Muteks:

  • Jednak w przeciwieństwie do monitorów mutex może służyć do synchronizowania wątków między procesami. W przypadku synchronizacji między procesami muteks nazywany jest nazwanym muteksem, ponieważ ma być używany w innej aplikacji, a zatem nie można go udostępniać za pomocą zmiennej globalnej lub statycznej. Musi mieć nadaną nazwę, aby obie aplikacje miały dostęp do tego samego obiektu mutex. W przeciwieństwie do tego klasa Mutex jest opakowaniem konstrukcji Win32. Chociaż jest silniejszy niż monitor, mutex wymaga przejść międzyoperacyjnych, które są bardziej kosztowne obliczeniowo niż te wymagane przez klasę Monitor.

Semafory (ranią mój mózg).

  • Użyj klasy Semaphore, aby kontrolować dostęp do puli zasobów. Wątki wprowadzają semafor, wywołując metodę WaitOne, która jest dziedziczona z klasy WaitHandle, i zwalniają semafor, wywołując metodę Release. Licznik na semaforze jest zmniejszany za każdym razem, gdy wątek wchodzi do semafora i zwiększany, gdy wątek zwalnia semafor. Gdy liczba wynosi zero, kolejne żądania są blokowane, dopóki inne wątki nie zwolnią semafora. Gdy wszystkie wątki zwolnią semafor, licznik osiągnie maksymalną wartość określoną podczas tworzenia semafora. Wątek może wielokrotnie wchodzić do semafora .. Klasa Semaphore nie wymusza tożsamości wątku w WaitOne lub Release .. odpowiedzialność programistów za niezakłócanie. Semafory są dwojakiego rodzaju: semafory lokalne i nazwanesemafory systemowe. Jeśli utworzysz obiekt Semaphore przy użyciu konstruktora, który akceptuje nazwę, jest on powiązany z semaforem systemu operacyjnego o tej nazwie. Nazwane semafory systemowe są widoczne w całym systemie operacyjnym i można ich używać do synchronizacji działań procesów. Lokalny semafor istnieje tylko w twoim procesie. Może być używany przez dowolny wątek w procesie, który ma odwołanie do lokalnego obiektu Semaphore. Każdy obiekt Semaphore jest oddzielnym semaforem lokalnym.

STRONA DO CZYTANIA - Synchronizacja wątków (C #)

Gishu
źródło
18
Twierdzisz, że Monitornie pozwala na komunikację, jest niepoprawne; nadal możesz Pulseitd. zMonitor
Marc Gravell
3
Sprawdź alternatywny opis Semaforów - stackoverflow.com/a/40473/968003 . Pomyśl o semaforach jak o bramkarzach w nocnym klubie. W klubie jest jednocześnie dozwolona liczba osób. Jeśli klub jest pełny, nikt nie może wejść, ale jak tylko jedna osoba opuści, druga osoba może wejść.
Alex Klaus,
31

Re „Korzystanie z innych klas synchronizacji .Net” - kilka innych, o których warto wiedzieć:

Istnieje również więcej konstrukcji blokujących (o niskim narzutie) w CCR / TPL ( Parallel Extensions CTP) - ale IIRC, zostaną one udostępnione w .NET 4.0

Marc Gravell
źródło
Więc jeśli chcę prostej komunikacji sygnałowej (powiedzmy zakończenie operacji asynchronicznej) - powinienem Monitor.Pulse? lub użyj SemaphoreSlim lub TaskCompletionSource?
Vivek,
Użyj TaskCompletionSource do operacji asynchronicznej. Zasadniczo przestań myśleć o wątkach i zacznij myśleć o zadaniach (jednostkach pracy). Wątki są szczegółem implementacji i nie mają znaczenia. Zwracając TCS, możesz zwracać wyniki, błędy lub obsługiwać anulowanie i można je łatwo komponować z innymi operacjami asynchronicznymi (takimi jak async await lub ContinueWith).
Simon Gillbee
14

Jak stwierdzono w ECMA i jak można zauważyć z metod Reflected, instrukcja lock jest w zasadzie równoważna z

object obj = x;
System.Threading.Monitor.Enter(obj);
try {
   
}
finally {
   System.Threading.Monitor.Exit(obj);
}

Z powyższego przykładu widzimy, że Monitory mogą blokować obiekty.

Mutexe są przydatne, gdy potrzebujesz synchronizacji międzyprocesowej, ponieważ mogą blokować identyfikator ciągu. Ten sam identyfikator ciągu może być używany przez różne procesy w celu uzyskania blokady.

Semafory są jak muteksy na sterydach, umożliwiają równoczesny dostęp, zapewniając maksymalną liczbę jednoczesnych dostępów ”. Po osiągnięciu limitu semafor zaczyna blokować dalszy dostęp do zasobu, dopóki jeden z wywołujących nie zwolni semafora.

arul
źródło
5
Ten cukier składniowy został nieznacznie zmieniony w C # 4 Sprawdź blogs.msdn.com/ericlippert/archive/2009/03/06/…
Peter Gfader
14

Zrobiłem zajęcia i obsługę CLR dla wątków w DotGNU i mam kilka przemyśleń ...

O ile nie potrzebujesz blokad krzyżowych, zawsze powinieneś unikać używania Mutex & Semaphores. Te klasy w .NET są opakowaniami wokół Win32 Mutex i Semaphores i mają dość dużą wagę (wymagają przełączania kontekstu na jądro, co jest drogie - szczególnie jeśli blokada nie jest kwestionowana).

Jak wspomnieliśmy inni, instrukcja blokady C # jest magią kompilatora dla Monitor.Enter i Monitor.Exit (istniejąca w try / w końcu).

Monitory mają prosty, ale potężny mechanizm sygnału / oczekiwania, którego Muteksy nie mają za pośrednictwem metod Monitor.Pulse / Monitor.Wait. Odpowiednikiem Win32 byłyby obiekty zdarzeń za pośrednictwem CreateEvent, które faktycznie istnieją również w .NET jako WaitHandles. Model Pulse / Wait jest podobny do uniksowych pthread_signal i pthread_wait, ale są szybsze, ponieważ mogą być działaniami całkowicie w trybie użytkownika w przypadku niekontrolowanym.

Monitor.Pulse / Wait jest prosty w użyciu. W jednym wątku blokujemy obiekt, sprawdzamy flagę / stan / właściwość i jeśli nie tego oczekujemy, wywołujemy Monitor.Wait, który zwalnia blokadę i czeka na wysłanie impulsu. Kiedy czas oczekiwania powraca, wykonujemy pętlę i ponownie sprawdzamy flagę / stan / właściwość. W drugim wątku blokujemy obiekt za każdym razem, gdy zmieniamy flagę / stan / właściwość, a następnie wywołujemy PulseAll, aby obudzić wątki nasłuchujące.

Często chcemy, aby nasze klasy były bezpieczne dla wątków, więc umieszczamy blokady w naszym kodzie. Jednak często jest tak, że nasza klasa będzie używana tylko przez jeden wątek. Oznacza to, że blokady niepotrzebnie spowalniają nasz kod ... w tym miejscu sprytne optymalizacje w środowisku CLR mogą pomóc poprawić wydajność.

Nie jestem pewien co do implementacji blokad firmy Microsoft, ale w DotGNU i Mono flaga stanu blokady jest przechowywana w nagłówku każdego obiektu. Każdy obiekt w .NET (i Javie) może stać się blokadą, więc każdy obiekt musi to obsługiwać w swoim nagłówku. W implementacji DotGNU istnieje flaga, która pozwala na użycie globalnej tablicy hashy dla każdego obiektu używanego jako blokada - ma to tę zaletę, że eliminuje 4-bajtowe obciążenie dla każdego obiektu. Nie jest to dobre rozwiązanie dla pamięci (szczególnie dla systemów wbudowanych, które nie są mocno wątkowe), ale ma wpływ na wydajność.

Zarówno Mono, jak i DotGNU skutecznie używają muteksów do wykonywania blokowania / oczekiwania, ale używają operacji porównania i wymiany w stylu spinlock, aby wyeliminować potrzebę rzeczywistego wykonywania twardych blokad, chyba że jest to naprawdę konieczne:

Przykład implementacji monitorów można zobaczyć tutaj:

http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup

tumtumtum
źródło
9

Dodatkowym zastrzeżeniem dotyczącym blokowania dowolnego współużytkowanego Mutexu zidentyfikowanego za pomocą identyfikatora ciągu jest to, że domyślnie będzie to mutex „Lokalny \” i nie będzie współdzielony między sesjami w środowisku serwera terminali.

Przedrostek identyfikatora w postaci ciągu znaków „Global \” zapewnia odpowiednią kontrolę dostępu do współdzielonych zasobów systemowych. Właśnie napotkałem całą masę problemów z synchronizacją komunikacji z usługą działającą w ramach konta SYSTEM, zanim zdałem sobie z tego sprawę.

nvuono
źródło
5

Starałbym się unikać "lock ()", "Mutex" i "Monitor", jeśli możesz ...

Sprawdź nową przestrzeń nazw System.Collections.Concurrent w .NET 4
Ma kilka ładnych klas kolekcji bezpiecznych wątkowo

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

ConcurrentDictionary rządzi! nie ma już dla mnie ręcznego blokowania!

Peter Gfader
źródło
2
Unikaj blokady, ale używaj monitora? Czemu?
mafu
@mafutrct Ponieważ sam musisz zadbać o synchronizację.
Peter Gfader
Och, teraz rozumiem, chciałeś uniknąć WSZYSTKICH z trzech wymienionych pomysłów. Brzmiało to tak, jakbyś używał monitora, ale nie używał blokady / muteksu.
mafu
Nigdy nie używaj System.Collections.Concurrent. Są głównym źródłem warunków wyścigu, a także blokują wątek wywołujący.
Alexander Danilov
-2

W większości przypadków nie należy używać blokad (= Monitory) ani muteksów / semaforów. Wszystkie blokują bieżący wątek.

I zdecydowanie nie powinieneś używać System.Collections.Concurrent klas - są głównym źródłem warunków wyścigu, ponieważ nie obsługują transakcji między wieloma kolekcjami, a także blokują bieżący wątek.

Zaskakujące jest, że .NET nie ma skutecznych mechanizmów synchronizacji.

Zaimplementowałem kolejkę szeregową z GCD ( Objc/Swiftworld) na C # - bardzo lekkie, nieblokujące narzędzie do synchronizacji wykorzystujące pulę wątków, z testami.

W większości przypadków jest to najlepszy sposób na zsynchronizowanie wszystkiego - od dostępu do bazy danych (hello sqlite) po logikę biznesową.

Aleksandra Daniłowa
źródło