Wpisany przez Tomasz Lubiński,
26 lipca 2005 20:54
Algorytm ten wykorzystuje się do rozwiązania problemu wzajemnego wykluczania. Z zagadnieniem tym mamy do czynienia na przykład, gdy dwa równolegle wykonywane procesy korzystają ze wspólnej pamięci. Przeprowadźmy symulację działania dwóch równolegle wykonywanych programów, z których jeden zwiększa wartość współdzielonej zmiennej w o 2, a drugi o 3 (wartość początkowa w to 0). Najpierw obydwa procesy pobierają wartość zmiennej w po czym pierwszy zwiększa jej wartość o 2 (0+2=2), a drugi o 3 (0+3=3). Po wykonaniu operacji procesy odsyłają wyniki. Jeżeli proces pierwszy zapisze swój wynik szybciej do zmiennej współdzielonej końcowym wynikiem będzie 3, jeżeli wolniej niż proces drugi w wyniku otrzymamy 2. W przypadku, gdy procesy wykonają obliczenia w sposób, który wykluczy jednoczesny dostęp do zmiennej współdzielonej wynik będzie prawidłowy.
Zapiszmy teraz problem wzajemnego wykluczania w sposób formalny:
Dany jest zbiór procesów sekwencyjnych komunikujących się przez wspólną pamięć. Każdy z procesów zawiera sekcję krytycznę, w której następuje dostęp do wspólnej pamięci. Procesy te są procesami cyklicznymi. Zakłada sie ponadto:
Założenia dotyczące algorytmu są następujące:
Algorytm pozwala rozwiązać problem wzajemnego wykluczania dla n procesów.
Do komunikacji między równolegle wykonującymi się procesami służą 2 współdzielone zmienne tablicowe:
num[1..n] - która dla każdego procesu ubiegającego się o wejście do sekcji krytycznej ma nadaną wartość, która określa miejsce procesu w kolejce do sekcji, (np. num[j]=3 oraz num[j-1]=4, oznacza tyle, że proces j-ty ma pierwszeństwo w dostępie do sekcji krytycznej przed procesem j-1),
choosing[1..n] - wartość 1 dla choosing[j] oznacza, że j-ty proces nadaje sobie kolejny numer w kolejce procesów chcących uzyskać dostęp do sekcji krytycznej. Wartość 0 oznacza, że nie ubiega się w danej chwili o przyznanie numeru w kolejce do sekcji (albo ma już nadany taki numer, albo w ogóle nie chce wejść do sekcji krytycznej).
Algorytm Lamporta'a ma następującą postać (zmienna i przyjmuje wartość będącą numerem procesu, czyli w procesie pierwszym 1, w procesie drugim 2, ...):
Proces, który chce wejść do sekcji krytycznej, na początku musi przyznać sobie numer w kolejce procesów czekających na wejście do tej sekcji. W tym celu ustawia zmienną choosing[i] na wartość 1, ktora informuje inne procesy, że ustala on właśnie swój numer w kolejce. Następnie w pętli for, szuka maksymalnej wartości zmiennej num[], spośród wszystkich pozostałych procesów. Po znalezieniu tej wartości zwiększa ją o jeden i przypisuje sobie numer w kolejce, w zmiennej współdzielonej num[i]. Następnie zdejmuje flagę informując, w ten sposób inne procesy, o tym, że znalazł już swój numer w kolejce procesów czekających na wejście do sekcji krytycznej (choosing[i] na wartość 0).
Teraz w drugiej pętli for kolejno dla wszystkich procesów (z wyjątkiem siebie samego) proces czeka na procesy, które nadają sobie numer w kolejce (pierwsza pętla repeat), oraz na procesy, które znajdują się przed nim (druga pętla repeat). Wyjaśnienia wymaga tutaj druga pętla repeat. Kończy ona swoje wykonywanie w przypadku, gdy proces nie chce wejść do sekcji krytycznej lub w kolejce procesów znajduje się za naszym procesem. Porównanie (mine,i)<(other,k) czytać należy jako prawdziwe gdy: (mine<other bez względu na wartość i oraz k), lub gdy (mine=other oraz i<k). Tą konstrukcję należy użyć, gdyż nie możemy zagwarantować, żę dwa różne procesy nie przyznadzą sobie równych wartości w tablicy num[].
Po wyjściu z sekcji krytycznej proces ustawia wartość zmiennej współdzielonej num[i], na wartość 0, ustępując tym samym miejsce procesom czekającym w kolejce.
Problemem, podczas implementacji algorytmu, może być kwestia przekroczenia zakresu w tablicy num[]. Może stać się tak, gdy do kolejki procesów czekających na wejście do sekcji krytycznej ciągle dołączają nowe, które przyznają sobie kolejne numery w kolejce. By rozwiązać ten problem, można co jakiś czas "wyrzucać" procesy przed algorytm, zerować wartość zmiennej num[] i wpuszczać je ponownie. Można również zastosować odmienne podejście: jeżeli w tablicy num[] są przydzielone już liczby np. od 100 do maksimum zakresu, to należy kolejnemu procesowi przydzielić numer 1 i traktować tą wartość tak jakby była ona największa.
Zapiszmy teraz problem wzajemnego wykluczania w sposób formalny:
Dany jest zbiór procesów sekwencyjnych komunikujących się przez wspólną pamięć. Każdy z procesów zawiera sekcję krytycznę, w której następuje dostęp do wspólnej pamięci. Procesy te są procesami cyklicznymi. Zakłada sie ponadto:
- zapis i odczyt wspólnych danych jest operacją niepodzielną, a próba jednoczesnych zapisów lub odczytów realizowana jest sekwencyjnie w nieznanej kolejności
- sekcje krytyczne nie mają priorytetu,
- względne prędkości wykonywania procesów są nieznane,
- proces może zostać zawieszony poza sekcją krytyczna,
- procesy realizujące instrukcje poza sekcją krytyczną nie mogą uniemożliwiać innym procesom wejścia do sekcji krytycznej,
- procesy powinny uzyskać dostęp do sekcji krytycznej w skończonym czasie.
Założenia dotyczące algorytmu są następujące:
Algorytm pozwala rozwiązać problem wzajemnego wykluczania dla n procesów.
Do komunikacji między równolegle wykonującymi się procesami służą 2 współdzielone zmienne tablicowe:
num[1..n] - która dla każdego procesu ubiegającego się o wejście do sekcji krytycznej ma nadaną wartość, która określa miejsce procesu w kolejce do sekcji, (np. num[j]=3 oraz num[j-1]=4, oznacza tyle, że proces j-ty ma pierwszeństwo w dostępie do sekcji krytycznej przed procesem j-1),
choosing[1..n] - wartość 1 dla choosing[j] oznacza, że j-ty proces nadaje sobie kolejny numer w kolejce procesów chcących uzyskać dostęp do sekcji krytycznej. Wartość 0 oznacza, że nie ubiega się w danej chwili o przyznanie numeru w kolejce do sekcji (albo ma już nadany taki numer, albo w ogóle nie chce wejść do sekcji krytycznej).
Algorytm Lamporta'a ma następującą postać (zmienna i przyjmuje wartość będącą numerem procesu, czyli w procesie pierwszym 1, w procesie drugim 2, ...):
procedure process_i;
var //zmienne lokalne procesu
test, k, mine, other, temp: integer;
begin
while true do
begin
{część prgramu przed sekcją krytyczną}
choosing[i]:=1;
for k:=1 to n do
if k<>i then
begin
temp:=num[k];
mine:=max(mine, temp);
end;
mine:=mine+1;
num[i]:=mine;
choosing[i]:=0;
for k:=1 to n do
if k<>i then
begin
repeat
test:=choosing[k];
until test=0
repeat
other:=num[k];
until other=0 or (mine,i)<(other,k)
end;
{sekcja krytyczna}
num[i]:=0;
{pozostała część programu}
end;
end;
Proces, który chce wejść do sekcji krytycznej, na początku musi przyznać sobie numer w kolejce procesów czekających na wejście do tej sekcji. W tym celu ustawia zmienną choosing[i] na wartość 1, ktora informuje inne procesy, że ustala on właśnie swój numer w kolejce. Następnie w pętli for, szuka maksymalnej wartości zmiennej num[], spośród wszystkich pozostałych procesów. Po znalezieniu tej wartości zwiększa ją o jeden i przypisuje sobie numer w kolejce, w zmiennej współdzielonej num[i]. Następnie zdejmuje flagę informując, w ten sposób inne procesy, o tym, że znalazł już swój numer w kolejce procesów czekających na wejście do sekcji krytycznej (choosing[i] na wartość 0).
Teraz w drugiej pętli for kolejno dla wszystkich procesów (z wyjątkiem siebie samego) proces czeka na procesy, które nadają sobie numer w kolejce (pierwsza pętla repeat), oraz na procesy, które znajdują się przed nim (druga pętla repeat). Wyjaśnienia wymaga tutaj druga pętla repeat. Kończy ona swoje wykonywanie w przypadku, gdy proces nie chce wejść do sekcji krytycznej lub w kolejce procesów znajduje się za naszym procesem. Porównanie (mine,i)<(other,k) czytać należy jako prawdziwe gdy: (mine<other bez względu na wartość i oraz k), lub gdy (mine=other oraz i<k). Tą konstrukcję należy użyć, gdyż nie możemy zagwarantować, żę dwa różne procesy nie przyznadzą sobie równych wartości w tablicy num[].
Po wyjściu z sekcji krytycznej proces ustawia wartość zmiennej współdzielonej num[i], na wartość 0, ustępując tym samym miejsce procesom czekającym w kolejce.
Problemem, podczas implementacji algorytmu, może być kwestia przekroczenia zakresu w tablicy num[]. Może stać się tak, gdy do kolejki procesów czekających na wejście do sekcji krytycznej ciągle dołączają nowe, które przyznają sobie kolejne numery w kolejce. By rozwiązać ten problem, można co jakiś czas "wyrzucać" procesy przed algorytm, zerować wartość zmiennej num[] i wpuszczać je ponownie. Można również zastosować odmienne podejście: jeżeli w tablicy num[] są przydzielone już liczby np. od 100 do maksimum zakresu, to należy kolejnemu procesowi przydzielić numer 1 i traktować tą wartość tak jakby była ona największa.
Implementacje
Autor | Język programowania | Komentarz | Otwórz | Pobierz | Ocena |
Tomasz Lubiński | C/C++ | Linux | .cpp | .cpp | ***** / 1 |
Poprawiony: 11 czerwca 2011 19:10