Istnieją dwa podstawowe pojęcia dotyczące tworzenia programów współbieżnych - synchronizacja i wzajemne wykluczanie. Przekonamy się, jak te dwa rodzaje zamków (semafory są bardziej ogólnym rodzajem mechanizmu blokującego) pomagają nam osiągnąć synchronizację i wzajemne wykluczenie.
Semafor składa się z dwóch części: licznika i listy zadań oczekujących na dostęp do określonego zasobu. Semafor wykonuje dwie operacje: wait (P) [to jest jak zdobywanie zamka] i zwolnienie (V) [podobnie jak zwolnienie blokady] - są to jedyne dwie operacje, które można wykonać na semaforze. W semaforze binarnym licznik logicznie przyjmuje wartości od 0 do 1. Można go uznać za podobny do blokady z dwiema wartościami: otwórz/zamknij. Semafor zliczający ma wiele wartości do zliczenia.
Ważne jest, aby zrozumieć, że licznik semaforów śledzi liczbę zadań, które nie muszą blokować, tj. Mogą robić postępy. Zadania blokują i dodają się do listy semaforów tylko wtedy, gdy licznik jest zerowy. W związku z tym zadanie zostanie dodane do listy w procedurze P(), jeśli nie może się rozwijać i "uwalniać" przy użyciu procedury V().
Teraz jest dość oczywiste, aby zobaczyć, jak binarne semafory mogą być używane do rozwiązywania synchronizacji i wzajemnego wykluczania - są to w zasadzie zamki.
np. Synchronizacja:
thread A{
semaphore &s; //locks/semaphores are passed by reference! think about why this is so.
A(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
;// some block of code B2
...
}
//thread B{
semaphore &s;
B(semaphore &s): s(s){} //constructor
foo(){
...
...
// some block of code B1
s.V();
..
}
main(){
semaphore s(0); // we start the semaphore at 0 (closed)
A a(s);
B b(s);
}
W powyższym przykładzie, B2 można tylko wykonać po B1 zakończył realizację. Powiedzmy, że wątek A przychodzi jako pierwszy - dostaje się do sem.P() i czeka, ponieważ licznik jest równy 0 (zamknięty). Wątek B pojawia się, kończy B1, a następnie uwalnia wątek A - który kończy B2. Więc osiągamy synchronizację.
Teraz przyjrzyjmy wzajemnego wykluczania z semafora binarnego:
thread mutual_ex{
semaphore &s;
mutual_ex(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
//critical section
s.V();
...
...
s.P();
//critical section
s.V();
...
}
main(){
semaphore s(1);
mutual_ex m1(s);
mutual_ex m2(s);
}
Wzajemne wykluczanie jest dość prosta, jak również - m1 i m2 nie może wejść do sekcji krytycznej w tym samym czasie. Zatem każdy wątek używa tego samego semafora, aby zapewnić wzajemne wykluczenie dla dwóch kluczowych sekcji. Czy możliwa jest większa współbieżność? Zależy od krytycznych sekcji. (Pomyśl o tym, jak jeszcze można użyć semaforów osiągnąć wzajemne wykluczanie .. podpowiedź: czy koniecznie trzeba tylko użyć jeden semafora?)
Liczenie semafor: Semafor z więcej niż jedną wartość. Spójrzmy na to, co to oznacza - blokada z więcej niż jedną wartością? Tak otwarty, zamknięty i ... hmm. Na czym polega wielostopniowe blokowanie we wzajemnym wykluczeniu lub synchronizacji?
Weźmy łatwiejszy z dwóch:
Synchronizacja za pomocą semafora licznikami: powiedzmy, że mają 3 zadania - # 1 i 2, które mają być wykonywane po 3. W jaki sposób zaprojektować synchronizację?
thread t1{
...
s.P();
//block of code B1
thread t2{
...
s.P();
//block of code B2
thread t3{
...
//block of code B3
s.V();
s.V();
}
Więc jeśli semafor zaczyna zamknięty, upewnić się, że T1 i T2 blok, dodawane do listy semafora. Następnie przychodzi wszystko ważne t3, kończy swoją działalność i zwalnia t1 i t2. W jakiej kolejności są uwalniani? Zależy od implementacji listy semaforów. Może to być FIFO, może być oparty na określonym priorytecie, itp. (Uwaga: myśleć o tym, jak zorganizować swoją P i V; s gdybyś chciał t1 i t2 być wykonany w jakimś konkretnym celu, a jeśli nie byli świadomi realizacji semafora)
(Sprawdzaj: Co się stanie, jeśli liczba V jest większa niż liczba P'S)
Wzajemne wykluczanie Korzystanie licząc semafory: Chciałbym Cię do skonstruowania własnego Pseudokod dla tego (sprawia, że można zrozumieć rzeczy lepiej!) - ale podstawowa koncepcja jest taka: semafor liczący licznika = N pozwala N zadaniom swobodnie wejść do sekcji krytycznej. Oznacza to, że masz N zadań (lub wątków, jeśli chcesz) wejść do sekcji krytycznej, ale zadanie N + 1 zostanie zablokowane (trafia na naszą ulubioną listę zablokowanych zadań), a tylko zostaje przepuszczone, gdy ktoś V jest semaforem przynajmniej raz. Tak więc licznik semaforów, zamiast wahać się między 0 a 1, teraz przechodzi od 0 do N, dzięki czemu N zadań może swobodnie wchodzić i wychodzić, nie blokując nikogo!
Po co ci taki dziwaczny zamek?Czy całkowite wykluczenie nie oznacza, że więcej niż jeden facet ma dostęp do zasobu? Pomyśl. (Podpowiedź ... Nie zawsze mają tylko jeden napędu w komputerze, czy ty ...?)
Aby zastanowić: Czy wzajemne wykluczanie osiągnięte przez posiadające liczenia semafora sam? Co jeśli masz 10 wystąpień zasobu i 10 wątków przychodzi (przez semafor zliczający) i próbujesz użyć pierwszego wystąpienia?
Dobrym przykładem użycia semafora zliczającego jest to, czy chcesz kontynuować po kilku różnych zdarzeniach. Na przykład twój program uruchamia się i inicjuje 5 różnych odczytów bazy asynchronicznej i nie powinien być kontynuowany, dopóki wszystkie 5 nie zostanie ukończone. Zwiększałbyś semafor na początku każdego odczytu i natychmiast blokowałeś 'semafor == 0'. Po zakończeniu każdego odczytu zmniejsz wartość semafora. Twój start będzie kontynuowany po zakończeniu ładowania danych. –