Programowanie współbieżne i rozproszone/PWR Ćwiczenia 1: Różnice pomiędzy wersjami
m Zastępowanie tekstu – „ </math>” na „</math>” |
|||
(Nie pokazano 5 wersji utworzonych przez 2 użytkowników) | |||
Linia 36: | Linia 36: | ||
; Ćwiczenie | ; Ćwiczenie | ||
: Odpowiedz na poniższe pytania przed przystąpieniem do dalszej części ćwiczenia. | : Odpowiedz na poniższe pytania przed przystąpieniem do dalszej części ćwiczenia. | ||
Jeśli masz kłopoty ze znalezieniem odpowiedzi przeczytaj jeszcze raz [[fragment wykładu]]. | Jeśli masz kłopoty ze znalezieniem odpowiedzi, przeczytaj jeszcze raz [[fragment wykładu]]. | ||
# Czy protokoły wstępne i końcowe można pozostawić puste? | # Czy protokoły wstępne i końcowe można pozostawić puste? | ||
Linia 59: | Linia 59: | ||
<div class="mw-collapsible-content" style="display:none">Konflikt rozwiązuje sprzęt za pomocą ''arbitra pamięci''. | <div class="mw-collapsible-content" style="display:none">Konflikt rozwiązuje sprzęt za pomocą ''arbitra pamięci''. | ||
Jest to układ sprzętowy, który realizuje wzajemne wykluczanie na poziomie pojedynczych komórek pamięci. | Jest to układ sprzętowy, który realizuje wzajemne wykluczanie na poziomie pojedynczych komórek pamięci. | ||
Jednoczesne odwołania do tej samej komórki pamięci zostaną w jakiś | Jednoczesne odwołania do tej samej komórki pamięci zostaną w jakiś | ||
nieznany z góry | nieznany z góry sposób uporządkowane w czasie i wykonane. W dalszej | ||
części rozważań zakładamy istnienie arbitra pamięci i jego poprawne | części rozważań zakładamy istnienie arbitra pamięci i jego poprawne | ||
działanie. | działanie. | ||
Linia 74: | Linia 74: | ||
pustą pętlę, której jedynym zadaniem jest cykliczne sprawdzanie warunku | pustą pętlę, której jedynym zadaniem jest cykliczne sprawdzanie warunku | ||
na wejście do sekcji krytycznej. Proces, który skorzysta z | na wejście do sekcji krytycznej. Proces, który skorzysta z | ||
<tt>sekcji krytycznej</tt> przekazuje pierwszeństwo drugiemu. | <tt>sekcji krytycznej</tt>, przekazuje pierwszeństwo drugiemu. | ||
; Ćwiczenie | ; Ćwiczenie | ||
: Zapisz treść procesów | : Zapisz treść procesów. | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> Odpowiedź | |||
Treść procesów wygląda następująco: | <div class="mw-collapsible-content" style="display:none">Treść procesów wygląda następująco: | ||
{| | {| | ||
| colspan="2" | | | colspan="2" | | ||
Linia 108: | Linia 109: | ||
'''end'''; | '''end'''; | ||
|} | |} | ||
</div></div> | |||
Zanim przystąpimy do analizy poprawności rozwiązania przypomnijmy założenia dotyczące | Zanim przystąpimy do analizy poprawności rozwiązania, przypomnijmy założenia dotyczące | ||
<tt>sekcji krytycznej</tt>, <tt>własnych spraw</tt> i systemu operacyjnego, który | <tt>sekcji krytycznej</tt>, <tt>własnych spraw</tt> i systemu operacyjnego, który | ||
nadzoruje wykonywanie procesów. | nadzoruje wykonywanie procesów. | ||
Linia 123: | Linia 125: | ||
## musi w skończonym czasie zakończyć je i przejść do protokołu wstępnego. | ## musi w skończonym czasie zakończyć je i przejść do protokołu wstępnego. | ||
## może zakończyć swoje działanie na skutek błędu. | ## może zakończyć swoje działanie na skutek błędu. | ||
## może zakończyć swoje działanie ale tylko w sposób poprawny. | ## może zakończyć swoje działanie, ale tylko w sposób poprawny. | ||
; Ćwiczenie | ; Ćwiczenie | ||
: Spróbuj przeanalizować samodzielnie powyższe rozwiązanie i odpowiedzieć na pytania: | : Spróbuj przeanalizować samodzielnie powyższe rozwiązanie i odpowiedzieć na pytania: | ||
# Czy ma ono własność bezpieczeństwa? | # Czy ma ono własność bezpieczeństwa? | ||
# Czy ma ono własność żywotność? | # Czy ma ono własność żywotność? | ||
# A może widzisz inne jego wady? | # A może widzisz inne jego wady? | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> Odpowiedź | |||
<div class="mw-collapsible-content" style="display:none"> | |||
Własnośc bezpieczeństwa '''jest''' zachowana --- nigdy oba procesy nie będą jednocześnie w | Własnośc bezpieczeństwa '''jest''' zachowana --- nigdy oba procesy nie będą jednocześnie w | ||
<tt>sekcji krytycznej</tt>. Mamy jednak niestety problem z żywotnością. | <tt>sekcji krytycznej</tt>. Mamy jednak niestety problem z żywotnością. | ||
Linia 148: | Linia 151: | ||
się we <tt> własnych sprawach</tt> jednego z nich wykonuje się dłużej niż <tt>własne sprawy</tt> | się we <tt> własnych sprawach</tt> jednego z nich wykonuje się dłużej niż <tt>własne sprawy</tt> | ||
drugiego), to proces "szybszy" będzie równał tempo pracy do wolniejszego. | drugiego), to proces "szybszy" będzie równał tempo pracy do wolniejszego. | ||
</div></div> | |||
=== Druga próba rozwiązania === | === Druga próba rozwiązania === | ||
Linia 193: | Linia 197: | ||
siebie. Jeśli jeden z nich nie chce korzystać z sekcji krytycznej lub | siebie. Jeśli jeden z nich nie chce korzystać z sekcji krytycznej lub | ||
awaryjnie zakończy swoje działanie we <tt>własnych sprawach</tt>, to drugi może | awaryjnie zakończy swoje działanie we <tt>własnych sprawach</tt>, to drugi może | ||
swobodnie wchodzić do rejonu krytycznego | swobodnie wchodzić do rejonu krytycznego ile razy zechce. | ||
; Ćwiczenie (ukrywajka) | ; Ćwiczenie (ukrywajka) | ||
Linia 204: | Linia 208: | ||
przypisaniem <tt> jest2 := true</tt> a przypisaniem <tt> jest2 := false </tt>. | przypisaniem <tt> jest2 := true</tt> a przypisaniem <tt> jest2 := false </tt>. | ||
Po skończonym czasie wyjdzie z sekcji krytycznej i ustawi swoją | Po skończonym czasie wyjdzie z sekcji krytycznej i ustawi swoją | ||
zmienną <tt>jest2</tt> na <tt>false</tt> pozwalając pierwszemu procesowi wyjść z jałowej pętli i wejść do sekcji krytycznej. Niestety, przy pewnych złośliwych przeplotach może się zdarzyć, że do rejonu krytycznego wejdą oba procesy.</div></div> | zmienną <tt>jest2</tt> na <tt>false</tt>, pozwalając pierwszemu procesowi wyjść z jałowej pętli i wejść do sekcji krytycznej. Niestety, przy pewnych złośliwych przeplotach może się zdarzyć, że do rejonu krytycznego wejdą oba procesy.</div></div> | ||
; Ćwiczenie (ukrywajka) | ; Ćwiczenie (ukrywajka) | ||
Linia 244: | Linia 248: | ||
Jak widać w krokach 2 i 4, gdy procesy sprawdzają warunki pętli zmienne mają jeszcze wartość | Jak widać w krokach 2 i 4, gdy procesy sprawdzają warunki pętli zmienne mają jeszcze wartość | ||
<tt>false</tt>. Przyczyną takiej sytuacji jest zbyt późne ustawienie zmiennych | <tt>false</tt>. Przyczyną takiej sytuacji jest zbyt późne ustawienie zmiennych | ||
logicznych. Proces już "prawie" był w sekcji | logicznych. Proces już "prawie" był w sekcji krytycznej (bo przeszedł przez | ||
wstrzymującą go pętlę i nic go teraz nie może już powstrzymać), | wstrzymującą go pętlę i nic go teraz nie może już powstrzymać), | ||
a jeszcze nie poinformował (tj. nie ustawił swojej zmiennej <tt>jest</tt>) | a jeszcze nie poinformował (tj. nie ustawił swojej zmiennej <tt>jest</tt>) | ||
Linia 303: | Linia 307: | ||
: Wskaż scenariusz pokazujący brak żywotności. | : Wskaż scenariusz pokazujący brak żywotności. | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed">Odpowiedź <div class="mw-collapsible-content" style="display:none"> | |||
Oto scenariusz (przeplot), który doprowadza do sytuacji, gdy oba procesy | Oto scenariusz (przeplot), który doprowadza do sytuacji, gdy oba procesy | ||
chcą wejść do sekcji krytycznej, a nie mogą: | chcą wejść do sekcji krytycznej, a nie mogą: | ||
Linia 319: | Linia 324: | ||
W krokach '''5''' i '''6''' oba procesy sprawdzają warunki pętli, które są prawdziwe, | W krokach '''5''' i '''6''' oba procesy sprawdzają warunki pętli, które są prawdziwe, | ||
bo wcześniej zmienne <tt>chce</tt> zostały ustawione na <tt>true</tt>. | bo wcześniej zmienne <tt>chce</tt> zostały ustawione na <tt>true</tt>. | ||
</div></div> | |||
; Test wyboru | ; Test wyboru | ||
Linia 385: | Linia 391: | ||
; Ćwiczenie (ukrywajka) | ; Ćwiczenie (ukrywajka) | ||
: Znajdź | : Znajdź ten przeplot! | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed">Odpowiedź <div class="mw-collapsible-content" style="display:none"> | |||
Oto szukany przeplot. Po kroku '''10''' wykonujemy ponownie krok '''5''' i | Oto szukany przeplot. Po kroku '''10''' wykonujemy ponownie krok '''5''' i | ||
tak w pętli nieskończonej: | tak w pętli nieskończonej: | ||
Linia 414: | Linia 421: | ||
'''end'''; | '''end'''; | ||
|} | |} | ||
</div></div> | |||
; Test wielokrotnego wyboru | ; Test wielokrotnego wyboru | ||
: Co sądzisz o tym przeplocie: | : Co sądzisz o tym przeplocie: | ||
# W praktyce jest bardzo mało prawdopodobne, żeby procesy wykonywały poszczególne instrukcje w nieskończoność w takiej kolejności | # W praktyce jest bardzo mało prawdopodobne, żeby procesy wykonywały poszczególne instrukcje w nieskończoność w takiej kolejności; | ||
# W związku z powyższym program jest poprawny | # W związku z powyższym program jest poprawny; | ||
# Nie można uznać tego programu za poprawny | # Nie można uznać tego programu za poprawny. | ||
Ten program jest niepoprawny. Nie pomoże argumentacja, że błędny przeplot jest "w zasadzie" | Ten program jest niepoprawny. Nie pomoże argumentacja, że błędny przeplot jest "w zasadzie" | ||
Linia 432: | Linia 440: | ||
Utrzymujemy zmienne <tt>chce1</tt> i <tt>chce2</tt>, które | Utrzymujemy zmienne <tt>chce1</tt> i <tt>chce2</tt>, które | ||
oznaczają chęć wejścia procesu do sekcji krytycznej. Gdy oba | oznaczają chęć wejścia procesu do sekcji krytycznej. Gdy oba | ||
procesy chcą wejść do sekcji krytycznej rozstrzygamy konflikt za | procesy chcą wejść do sekcji krytycznej, rozstrzygamy konflikt za | ||
pomocą zmiennej <tt>kto_czeka</tt>. | pomocą zmiennej <tt>kto_czeka</tt>. | ||
; Ćwiczenie (ukrywajka lub jesli czas pozwoli Java script) | ; Ćwiczenie (ukrywajka lub jesli czas pozwoli Java script) | ||
: Spróbuj zapisać algorytm Petersena | : Spróbuj zapisać algorytm Petersena | ||
Java skrypt mógłby zawierać elementy algorytmu (przypisania, if, while, and) | Java skrypt mógłby zawierać elementy algorytmu (przypisania, if, while, and), | ||
z których można by skonstruować algorytm i oceniac | z których można by skonstruować algorytm i oceniac poprawność rozwiązania, | ||
wskazując na ewentualne braki żywotnosci czy bezpieczeństwa. | |||
Linia 502: | Linia 510: | ||
* ograniczenie do dwóch procesów | * ograniczenie do dwóch procesów | ||
''Aktywne oczekiwanie'' polega na tym, że proces, który nie może kontynuować wykonania i musi poczekać, robi to angażując czas procesora | ''Aktywne oczekiwanie'' polega na tym, że proces, który nie może kontynuować wykonania i musi poczekać, robi to angażując czas procesora poprzez ciągłe sprawdzanie pewnych warunków. Taki sposób oczekiwania w dalszym toku tych zajęć będzie '''niedopuszczalny'''. Jednak aby zatrzymać wykonywanie procesu w sposób nie angażujący procesora, jest potrzebne wsparcie systemu operacyjnego i pewne mechanizmy udostępniane przez system. Ponieważ dzisiaj zakładaliśmy brak jakichkolwiek mechanizmów synchronizacyjnych, więc aktywne oczekiwanie było jedyną metodą zatrzymania procesu. | ||
Z ograniczeniem do dwóch procesów można sobie poradzić. | Z ograniczeniem do dwóch procesów można sobie poradzić. | ||
Linia 529: | Linia 537: | ||
'''while'''. Czekać będzie ten, który jako ostatni wykona przypisanie na | '''while'''. Czekać będzie ten, który jako ostatni wykona przypisanie na | ||
zmienną <tt>kto_czeka</tt>. Zatem pętla zadziała jak bariera zatrzymująca | zmienną <tt>kto_czeka</tt>. Zatem pętla zadziała jak bariera zatrzymująca | ||
jeden proces. Umieszczenie ''n'' | jeden proces. Umieszczenie ''n-1'' takich barier spowoduje | ||
zatrzymanie ''n-1'' procesów i wpuszczenie do sekcji krytycznej co | zatrzymanie ''n-1'' procesów i wpuszczenie do sekcji krytycznej co | ||
najwyżej jednego z nich. | najwyżej jednego z nich. | ||
Linia 551: | Linia 559: | ||
chce[i] := true; | chce[i] := true; | ||
kto_czeka[bariera] := i; | kto_czeka[bariera] := i; | ||
'''while''' <math>\exists_{j<>i} </math> chce[j] >= bariera and (kto_czeka[bariera] = i) '''do''' {nic}; | '''while''' <math>\exists_{j<>i}</math> chce[j] >= bariera and (kto_czeka[bariera] = i) '''do''' {nic}; | ||
'''end''' | '''end''' | ||
<tt>sekcja krytyczna</tt>; | <tt>sekcja krytyczna</tt>; |
Aktualna wersja na dzień 10:48, 5 wrz 2023
Zadanie 1
Treść
Uruchamiamy współbieżnie dwa następujące procesy:
process P1; begin while true do begin własne_sprawy; protokół_wstępny; sekcja_krytyczna; protokół_końcowy; end end; |
process P2; begin while true do begin własne_sprawy; protokół_wstępny; sekcja_krytyczna; protokół_końcowy; end end; |
Chcemy zapewnić, że w tym samym czasie co najwyżej jeden z nich wykonuje fragment programu oznaczony jako sekcja_krytyczna. Jakie instrukcje należy umieścić w protokołach, aby zrealizować ten cel? Nie dysponujemy żadnymi mechanizmami synchronizacyjnymi, więc protokoły powinny umiejętnie wykorzystać zmienne globalne oraz instrukcje języka programowania.
- Ćwiczenie
- Odpowiedz na poniższe pytania przed przystąpieniem do dalszej części ćwiczenia.
Jeśli masz kłopoty ze znalezieniem odpowiedzi, przeczytaj jeszcze raz fragment wykładu.
- Czy protokoły wstępne i końcowe można pozostawić puste?
- Co oznacza bezpieczeństwo w przypadku tak sformułowanego zadania?
- A żywotność?
- Jaką nazwę nosi ten problem?
- Ćwiczenie
- Odpowiedz na poniższe pytania przed przystąpieniem do dalszej części ćwiczenia.
Jeśli masz kłopoty ze znalezieniem odpowiedzi przeczytaj jeszcze raz ten fragment wykładu.
- Co dzieje się, gdy dwa wykonujące się równolegle procesy w tej samej chwili chcą uzyskać dostęp do tej samej zmiennej, a zatem do tej samej komórki (tych samych komórek) pamięci?
Pierwsza próba rozwiązania
Spróbujmy rozwiązać problem wprowadzając zmienną globalną ktoczeka. Będzie ona przyjmować wartości 1 lub 2. Wartość 1 oznacza, że proces pierwszy musi poczekać na wejście do sekcji krytycznej, a prawo wejścia do niej ma proces drugi. Oczekiwanie na wejście realizujemy poprzez pustą pętlę, której jedynym zadaniem jest cykliczne sprawdzanie warunku na wejście do sekcji krytycznej. Proces, który skorzysta z sekcji krytycznej, przekazuje pierwszeństwo drugiemu.
- Ćwiczenie
- Zapisz treść procesów.
Zanim przystąpimy do analizy poprawności rozwiązania, przypomnijmy założenia dotyczące sekcji krytycznej, własnych spraw i systemu operacyjnego, który nadzoruje wykonywanie procesów.
- Test wielokrotnego wyboru
- Formułując problem wzajemnego wykluczania zakłada się, że:
- Każdy proces, który wszedł do sekcji krytycznej w skończonym czasie ją opuści.
- Proces może przebywać w sekcji krytycznej nie dłużej niż ustalony z góry czas.
- Proces nie może zakończyć się w sekcji krytycznej.
- Proces może się zakończyć w sekcji krytycznej ale tylko w sposób poprawny.
- Proces, który rozpoczął wykonywanie własnych_spraw:
- musi w skończonym czasie zakończyć je i przejść do protokołu wstępnego.
- może zakończyć swoje działanie na skutek błędu.
- może zakończyć swoje działanie, ale tylko w sposób poprawny.
- Ćwiczenie
- Spróbuj przeanalizować samodzielnie powyższe rozwiązanie i odpowiedzieć na pytania:
- Czy ma ono własność bezpieczeństwa?
- Czy ma ono własność żywotność?
- A może widzisz inne jego wady?
Druga próba rozwiązania
Spróbujmy podejść do problemu w inny sposób. Wprowadźmy dwie logiczne zmienne globalne: jest1 oznaczającą, że proces P1 jest w sekcji krytycznej i analogiczną zmienną jest2 dla procesu P2. Przed wejściem do sekcji krytycznej proces sprawdza, czy jego rywal jest już w sekcji krytycznej. Jeśli tak, to czeka. Gdy sekcja krytyczna się zwolni, proces ustawi swoją zmienną na true sygnalizując, że jest w sekcji, po czym wejdzie do niej.
var jest1: boolean := false; jest2: boolean := false; | |
process P1; begin while true do begin własne_sprawy; while jest2 do {nic}; jest1 := true; sekcja_krytyczna; jest1 := false; end end; |
process P2; begin while true do begin własne_sprawy; while jest1 do {nic}; jest2 := true; sekcja_krytyczna; jest2 := false; end end; |
Zauważmy, że to rozwiązanie nie uzależnia już procesów od siebie. Jeśli jeden z nich nie chce korzystać z sekcji krytycznej lub awaryjnie zakończy swoje działanie we własnych sprawach, to drugi może swobodnie wchodzić do rejonu krytycznego ile razy zechce.
- Ćwiczenie (ukrywajka)
- Czy to rozwiązanie jest
- bezpieczne?
- żywotne?
- Ćwiczenie (ukrywajka)
- Wskaż taki scenariusz numerując kolejne wykonywane fragmenty procesów.
Trzecia próba rozwiązania
Ponieważ zmiana wartości zmiennych globalnych w poprzednim rozwiązaniu została dokonana zbyt późno, więc spróbujmy zmienić kolejność czynności i ustawmy najpierw zmienne logiczne, a potem dopiero próbujmy przejść przez pętle. Teraz zmienne logiczne oznaczają chęć wejścia do sekcji krytycznej:
var chce1: boolean := false; chce2: boolean := false; | |
process P1; begin while true do begin własne_sprawy; chce1 := true; while chce2 do {nic}; sekcja_krytyczna; chce1 := false; end end; |
process P2; begin while true do begin własne_sprawy; chce2 := true; while chce1 do {nic}; sekcja_krytyczna; chce2 := false; end end; |
- Ćwiczenie (ukrywajka)
- Czy to rozwiązanie jest
- bezpieczne?
- żywotne?
- Ćwiczenie (ukrywajka)
- Wskaż scenariusz pokazujący brak żywotności.
- Test wyboru
- Przedstawiony tu scenariusz prowadzi do:
- zagłodzenia
- zakleszczenia
Otrzymane rozwiązanie jest zatem znowu niepoprawne!
Czwarta próba rozwiązania
Można próbować ratować sytuację zmuszając procesy do chwilowej rezygnacji z wejścia do sekcji i ustąpienia pierwszeństwa rywalowi:
var chce1: boolean := false; chce2: boolean := false; | |
process P1; begin while true do begin własne_sprawy; chce1 := true; while chce2 do begin chce1 := false; chce1 := true; end; sekcja_krytyczna; chce1 := false; end end; |
process P2; begin while true do begin własne_sprawy; chce2 := true; while chce1 do begin chce2 := false; chce2 := true; end; sekcja_krytyczna; chce2 := false; end end; |
- Test wyboru
- Ten program
- jest bez sensu --- po co zmieniać wartość zmiennej na <false> jeśli za chwilę przywraca się jej wartość true
- ma własność bezpieczeństwa i żywotności
- ma własność bezpieczeństwa, ale nie żywotności
- ma własność żywotności, ale nie bezpieczeństwa
- byłby poprawny, gdybyśmy mogli zatrzymać wykonanie procesów w pętli while na jakiś krótki czas
Choć program jest bezpieczny, to jednak niestety znów istnieje (bardzo) złośliwy przeplot, który powoduje brak żywotności.
- Ćwiczenie (ukrywajka)
- Znajdź ten przeplot!
- Test wielokrotnego wyboru
- Co sądzisz o tym przeplocie:
- W praktyce jest bardzo mało prawdopodobne, żeby procesy wykonywały poszczególne instrukcje w nieskończoność w takiej kolejności;
- W związku z powyższym program jest poprawny;
- Nie można uznać tego programu za poprawny.
Ten program jest niepoprawny. Nie pomoże argumentacja, że błędny przeplot jest "w zasadzie" nieprawdopodobny. Zgodnie z definicją poprawności, skoro istnieje scenariusz powodujący brak żywotności, to program jest niepoprawny.
Algorytm Petersena
Poprawne rozwiązanie znane pod nazwą algorytmu Petersena jest sprytnym połączeniem pierwszego rozwiązania z przedostatnim. Utrzymujemy zmienne chce1 i chce2, które oznaczają chęć wejścia procesu do sekcji krytycznej. Gdy oba procesy chcą wejść do sekcji krytycznej, rozstrzygamy konflikt za pomocą zmiennej kto_czeka.
- Ćwiczenie (ukrywajka lub jesli czas pozwoli Java script)
- Spróbuj zapisać algorytm Petersena
Java skrypt mógłby zawierać elementy algorytmu (przypisania, if, while, and), z których można by skonstruować algorytm i oceniac poprawność rozwiązania, wskazując na ewentualne braki żywotnosci czy bezpieczeństwa.
Oto poprawne rozwiązanie zadania:
var chce1: boolean := false; chce2: boolean := false; kto_czeka: 1..2 := 1; | |
process P1; begin while true do begin własne_sprawy; chce1 := true; kto_czeka := 1; while chce2 and (kto_czeka = 1) do {nic}; sekcja_krytyczna; chce1 := false; end end; |
process P2; begin while true do begin własne_sprawy; chce2 := true; kto_czeka := 2; while chce1 and (kto_czeka = 2) do sekcja_krytyczna; chce2 := false; end end; |
Przypomnijmy, że
- nie zakładamy atomowości (niepodzielności) poszczególnych instrukcji (w szczególności wyliczanie złożonych warunków logicznych nie musi odbywać się atomowo),
- ale zakładamy istnienie arbitra pamięci (może dojść do jednoczesnej próby zmiany wartości zmiennej kto_czeka)
Zauważmy, że w sytuacji, gdy tylko jeden proces rywalizuje o dostęp do sekcji krytycznej, to będzie on mógł do woli z niej korzystać, bo zmienna chce jego rywala ma cały czas wartość false. Dzięki temu unikamy ścisłego powiązania procesów, jak było to w rozwiązaniu pierwszym. Z drugiej strony, gdy oba procesy ciągle chcą korzystać z sekcji krytycznej, robią to na zmianę.
- Zadanie
Przeanalizuj algorytm Petersona pod kątem zmiany kolejności wykonywanych instrukcji:
- Czy można zamienić kolejność przypisań przed pętlą while?
- Czy można umieścić zmianę wartości zmiennej kto_czeka po wyjściu z sekcji krytycznej?
- Czy można zamienić kolejność sprawdzania warunków w pętli while?
- Czy można zainicjować zmienną kto_czeka na inne wartości?
- A zmienne chce1 i chce2?
Rozwiązania przedstaw prowadzącemu wraz z uzasadnieniem.
Algorytm Petersena dla dwóch procesów. Podsumowanie.
Algorytm Petersena w przedstawionej tu wersji ma dwie ważne wady:
- aktywne oczekiwanie
- ograniczenie do dwóch procesów
Aktywne oczekiwanie polega na tym, że proces, który nie może kontynuować wykonania i musi poczekać, robi to angażując czas procesora poprzez ciągłe sprawdzanie pewnych warunków. Taki sposób oczekiwania w dalszym toku tych zajęć będzie niedopuszczalny. Jednak aby zatrzymać wykonywanie procesu w sposób nie angażujący procesora, jest potrzebne wsparcie systemu operacyjnego i pewne mechanizmy udostępniane przez system. Ponieważ dzisiaj zakładaliśmy brak jakichkolwiek mechanizmów synchronizacyjnych, więc aktywne oczekiwanie było jedyną metodą zatrzymania procesu.
Z ograniczeniem do dwóch procesów można sobie poradzić.
Zadanie 2 (opcjonalne)
Rozwiązać problem wzajemnego wykluczania dla n procesów. Wartość n jest przy tym stałą równą co najmniej 2.
Rozwiązanie
Rozwiązanie uzyskuje się przez naturalne uogólnienie algorytmu Petersena. Zauważmy, że wykonanie następującego fragmentu kodu przez n procesów:
process P (i: 1..n); begin while true do begin własne_sprawy; chce[i] := true; kto_czeka := i; while chce[j] and (kto_czeka = i) do {nic};
przy zaciętej rywalizacji o dostęp do sekcji krytycznej spowoduje zatrzymanie jednego z nich w pętli while. Czekać będzie ten, który jako ostatni wykona przypisanie na zmienną kto_czeka. Zatem pętla zadziała jak bariera zatrzymująca jeden proces. Umieszczenie n-1 takich barier spowoduje zatrzymanie n-1 procesów i wpuszczenie do sekcji krytycznej co najwyżej jednego z nich.
Oczywiście konflikt przy każdej kolejnej barierze powinien być rozstrzygany przez inną zmienną, zatem kto_czeka powinno być teraz tablicą indeksowaną numerami barier. Zmienna chce jest tablicą (indeksowaną numerami procesów) i pamiętającą już nie tylko informacje o tym, czy proces chce wejść do rejonu krytycznego, ale o tym, do której bariery doszedł (lub 0, jeśli proces nie uczestniczy w wyścigu do rejonu krytycznego). Otrzymujemy w ten sposób schemat:
process P (i: 1..n); begin while true do begin własne_sprawy; for bariera := 1 to n - 1 do begin chce[i] := true; kto_czeka[bariera] := i; while chce[j] >= bariera and (kto_czeka[bariera] = i) do {nic}; end sekcja krytyczna; chce[i] := 0; end end
Wady tej wersji algorytmu Petersena to:
- Aktywne oczekiwanie.
- Znana z góry liczba procesów
- Duża złożoność (a więc i koszt) protokołu wstępnego.