Odpowiedzi:
Zamki służą do wzajemnego wykluczania. Jeśli chcesz mieć pewność, że fragment kodu jest niepodzielny, umieść wokół niego blokadę. Teoretycznie można by to zrobić za pomocą semafora binarnego, ale to jest szczególny przypadek.
Semafory i zmienne warunkowe opierają się na wzajemnym wykluczaniu zapewnianym przez blokady i służą do zapewniania zsynchronizowanego dostępu do współdzielonych zasobów. Mogą być używane do podobnych celów.
Zmienna warunkowa jest zwykle używana, aby uniknąć zajętego oczekiwania (wielokrotne zapętlanie podczas sprawdzania warunku) podczas oczekiwania na udostępnienie zasobu. Na przykład, jeśli masz wątek (lub wiele wątków), który nie może kontynuować, dopóki kolejka nie będzie pusta, podejście do zajętego oczekiwania polegałoby na zrobieniu czegoś takiego:
//pseudocode
while(!queue.empty())
{
sleep(1);
}
Problem polega na tym, że marnujesz czas procesora, kilkakrotnie sprawdzając stan tego wątku. Dlaczego zamiast tego nie mieć zmiennej synchronizacyjnej, która może być sygnalizowana, aby poinformować wątek, że zasób jest dostępny?
//pseudocode
syncVar.lock.acquire();
while(!queue.empty())
{
syncVar.wait();
}
//do stuff with queue
syncVar.lock.release();
Prawdopodobnie będziesz mieć wątek gdzieś indziej, który wyciąga rzeczy z kolejki. Kiedy kolejka jest pusta, może zadzwonić, syncVar.signal()
aby obudzić losowy wątek, który śpi syncVar.wait()
(lub zwykle jest również signalAll()
lubbroadcast()
metoda , aby obudzić wszystkie oczekujące wątki).
Generalnie używam takich zmiennych synchronizacyjnych, gdy jeden lub więcej wątków oczekuje na jeden określony warunek (np. Na pustą kolejkę).
Semafory mogą być używane w podobny sposób, ale myślę, że są lepiej używane, gdy masz współdzielony zasób, który może być dostępny i niedostępny na podstawie pewnej liczby całkowitej dostępnych rzeczy. Semafory są dobre w sytuacjach producenta / konsumenta, w których producenci alokują zasoby, a konsumenci je konsumują.
Pomyśl, czy masz automat sprzedający napoje gazowane. Jest tylko jedna maszyna do napojów i jest to wspólny zasób. Masz jeden wątek, który jest sprzedawcą (producentem), który jest odpowiedzialny za przechowywanie maszyny na stanie, i N nici, którzy są kupującymi (konsumentami), którzy chcą wydobyć napoje gazowane z maszyny. Liczba napojów gazowanych w maszynie to liczba całkowita, która będzie napędzać nasz semafor.
Każdy wątek kupującego (konsumenta), który przychodzi do automatu z napojami, wywołuje down()
metodę semafora , aby wziąć napój gazowany. Spowoduje to pobranie napoju gazowanego z maszyny i zmniejszenie liczby dostępnych napojów o 1. Jeśli są dostępne napoje gazowane, kod będzie po prostu działał pozadown()
instrukcją bez problemu. Jeśli napoje gazowane nie są dostępne, wątek będzie spał w tym miejscu, czekając na powiadomienie o ponownym udostępnieniu napoju gazowanego (gdy w urządzeniu będzie więcej napojów gazowanych).
Dostawca (producent) nici zasadniczo czekałby na opróżnienie automatu do napojów. Sprzedawca jest powiadamiany, kiedy ostatni napój gazowany jest pobierany z automatu (a jeden lub więcej konsumentów potencjalnie czeka na ich wyjęcie). Sprzedawca uzupełniłby semafor w automacie do napojówup()
metodą , dostępna liczba napojów gazowanych byłaby zwiększana za każdym razem, a tym samym oczekujące wątki konsumentów byłyby powiadamiane o dostępności większej ilości napojów gazowanych.
Te wait()
i signal()
metody zmiennej synchronizacji wydają się być ukryte wewnątrz down()
i up()
operacji semafora.
Z pewnością te dwie opcje pokrywają się. Istnieje wiele scenariuszy, w których semafor lub zmienna warunku (lub zestaw zmiennych warunkowych) mogą służyć Twoim celom. Zarówno semafory, jak i zmienne warunkowe są powiązane z obiektem blokady, którego używają do utrzymywania wzajemnego wykluczania, ale następnie zapewniają dodatkową funkcjonalność oprócz blokady do synchronizacji wykonywania wątków. To głównie do Ciebie należy ustalenie, który z nich jest najbardziej odpowiedni w Twojej sytuacji.
To niekoniecznie najbardziej techniczny opis, ale to ma sens w mojej głowie.
Pokażmy, co kryje się pod maską.
Zmienna warunkowa jest zasadniczo kolejką oczekiwania , która obsługuje operacje blokowania-oczekiwania i wznowienia, tj. Można umieścić wątek w kolejce oczekiwania i ustawić jego stan na BLOK, a następnie pobrać z niej wątek i ustawić jego stan na GOTOWY.
Zwróć uwagę, że aby użyć zmiennej warunkowej, potrzebne są dwa inne elementy:
Protokół staje się wtedy:
Semafor jest w zasadzie licznikiem + muteksem + kolejką oczekiwania. I może być używany bez zewnętrznych zależności. Możesz go używać jako muteksu lub jako zmiennej warunkowej.
Dlatego semafor można traktować jako bardziej wyrafinowaną strukturę niż zmienna warunkowa, podczas gdy ta ostatnia jest lżejsza i bardziej elastyczna.
Semafory mogą służyć do implementacji wyłącznego dostępu do zmiennych, jednak mają służyć do synchronizacji. Z drugiej strony muteksy mają semantykę ściśle związaną z wzajemnym wykluczaniem: tylko proces, który zablokował zasób, może go odblokować.
Niestety nie możesz zaimplementować synchronizacji z muteksami, dlatego mamy zmienne warunkowe. Zauważ również, że zmiennymi warunkowymi możesz odblokować wszystkie oczekujące wątki w tej samej chwili, używając odblokowania transmisji. Nie można tego zrobić za pomocą semaforów.
zmienne semafora i warunku są bardzo podobne i są używane głównie do tych samych celów. Istnieją jednak drobne różnice, które mogą sprawić, że jeden będzie lepszy. Na przykład, aby zaimplementować synchronizację barier, nie można byłoby użyć semafora, ale zmienna warunkowa jest idealna.
Synchronizacja z barierami polega na tym, że chcesz, aby wszystkie twoje wątki czekały, aż wszyscy dotrą do określonej części funkcji wątku. można to zrealizować poprzez posiadanie zmiennej statycznej, która jest początkowo wartością wszystkich wątków zmniejszanych przez każdy wątek, gdy osiągnie tę barierę. oznaczałoby to, że chcemy, aby każdy wątek spał, dopóki nie nadejdzie ostatni. semafor zrobiłby dokładnie odwrotnie! z semaforem, każdy wątek działałby dalej, a ostatni wątek (który ustawi wartość semafora na 0) przejdzie w stan uśpienia.
z drugiej strony zmienna warunkowa jest idealna. kiedy każdy wątek dociera do bariery, sprawdzamy, czy nasz licznik statyczny wynosi zero. jeśli nie, ustawiamy wątek w stan uśpienia za pomocą funkcji wait zmiennej warunku. kiedy ostatni wątek dotrze do bariery, wartość licznika zostanie zmniejszona do zera, a ten ostatni wątek wywoła funkcję sygnału zmiennej warunku, która obudzi wszystkie inne wątki!
I zapisuję zmienne warunków w ramach synchronizacji monitora. Generalnie postrzegałem semafory i monitory jako dwa różne style synchronizacji. Istnieją różnice między nimi pod względem tego, ile danych stanu jest z natury przechowywanych i jak chcesz modelować kod - ale tak naprawdę nie ma żadnego problemu, który można rozwiązać jednym, a nie drugim.
Mam tendencję do kodowania w kierunku formy monitora; w większości języków, w których pracuję, sprowadza się to do muteksów, zmiennych warunkowych i niektórych pomocniczych zmiennych stanu. Ale semafory też by to zrobiły.
Za wykorzystanie mutex
i conditional variables
są dziedziczone semaphore
.
mutex
tego, semaphore
korzysta z dwóch stanów: 0, 1condition variables
tej semaphore
kasie zastosowań.Są jak cukier syntaktyczny