Chapter 3. Semafory

Table of Contents

Dzielenie pasma łącza
Rozwiązanie poprawne
Rozwiązanie niepoprawne
Rozwiązanie poprawne korzystające z metody Przekazywania (Dziedziczenia) Sekcji Krytycznej
Semafor uogólniony
Grupowanie uczestników
Czytelnicy i pisarze
Obliczenia w wyłącznych grupach
Obliczenia we współbieżnych grupach

Dzielenie pasma łącza

Łącze internetowe jest dzielone między wielu użytkowników. Szerokość pasma wynosi N. Użytkownik chcąc korzystać z łącza, rezerwuje potrzebną szerokość (potrzebna_szerokosc>0 i potrzebna_szerokosc<=N) pasma. Użytkonik zwalnia zarezerwowaną szerokość pasma, gdy skończy z niego korzystać. Zsynchronizuj użytkowników, aby mogli korzystać z połączenia zgodnie ze swoimi potrzebami.

const 
  N = ...;
  LICZBA_UZYTKOWNIKOW = ...;

procedure rezerwacja_pasma(szerokosc_dla_uzytkownika: integer)
begin
...
end;

procedure zwalnianie_pasma(szerokosc_dla_uzytkownika: integer)
begin
...
end;

process Uzytkownik(i: integer)
var
  szerokosc_dla_uzytkownika: integer;
begin
  while (true) do
  begin
    wlasne_sprawy();
    szerokosc_dla_uzytkownika := losuj(1, N);
    rezerwacja_pasma(szerokosc_dla_uzytkownika);
    korzystanie_z_lacza();
    zwalnianie_pasma(szerokosc_dla_uzytkownika);
  end;
end;

var
  i: integer;
begin
  cobegin for i:=1 to LICZBA_UZYTKOWNIKOW do begin Uzytkownik(i); end; coend;
end;

Rozwiązanie poprawne

Do rozwiązania tego problemy można wykorzystać metodę Pojedynczego Przetwarzania Żądań. Przetwarzanie żadania rezerwacji opakowujemy semaforem binarnym, w ten sposób przetwarzamy pojedynczo żądania.

var
  szerokosc_niezarezerwowana: integer := N;
  szerokosc_potrzebna: integer := -1;
  rozpatrywanie_rezerwacji_pojedynczo: binary semaphore := 1;
  czekajacy_uzytkownik: binary semaphore := 0;
  ochrona: binary semaphore := 1;

procedure rezerwacja_pasma(szerokosc_dla_uzytkownika: integer)
begin
  P(rozpatrywanie_rezerwacji_pojedynczo);
  P(ochrona);
  if (szerokosc_dla_uzytkownika<szerokosc_niezarezerwowana) then
    begin
      szerokosc_potrzebna := szerokosc_dla_uzytkownika;
      V(ochrona);
      P(czekajacy_uzytkownik);
      P(ochrona);
    end;
  szerokosc_niezarezerwowana := szerokosc_niezarezerwowana - szerokosc_dla_uzytkownika;
  V(ochrona);
  V(rozpatrywanie_rezerwacji_pojedynczo); 
end;

procedure zwalnianie_pasma(szerokosc_dla_uzytkownika: integer)
begin
  P(ochrona);
  szerokosc_niezarezerwowana := szerokosc_niezarezerwowana + szerokosc_dla_uzytkownika;
  if (szerokosc_potrzebna<>-1 and szerokosc_niezarezerwowana>=szerokosc_potrzebna) then
  begin
    szerokosc_potrzebna:=-1;
    V(czekajacy_uzytkownik);
  end;
  V(ochrona);
end;

Rozwiązanie niepoprawne

Warto zwrócić także uwagę na błędną implementację, w której błąd jest bardzo subtelny.

var
  szerokosc_niezarezerwowana: integer := N;
  szerokosc_potrzebna: integer := -1;
  rozpatrywanie_rezerwacji_pojedynczo: binary semaphore := 1;
  czekajacy_uzytkownik: binary semaphore := 0;
  ochrona: binary semaphore := 1;

procedure rezerwacja_pasma(szerokosc_dla_uzytkownika: integer)
begin
  P(rozpatrywanie_rezerwacji_pojedynczo);
  P(ochrona);
  if (szerokosc_dla_uzytkownika<szerokosc_niezarezerwowana) then
    begin
      szerokosc_potrzebna := szerokosc_dla_uzytkownika;
      V(ochrona);
      P(czekajacy_uzytkownik);
      P(ochrona);
      szerokosc_potrzebna:=-1;{dodana instrukcja}
    end;
  szerokosc_niezarezerwowana := szerokosc_niezarezerwowana - szerokosc_dla_uzytkownika;
  V(ochrona);
  V(rozpatrywanie_rezerwacji_pojedynczo); 
end;

procedure zwalnianie_pasma(szerokosc_dla_uzytkownika: integer)
begin
  P(ochrona);
  szerokosc_niezarezerwowana := szerokosc_niezarezerwowana + szerokosc_dla_uzytkownika;
  if (szerokosc_potrzebna<>-1 and szerokosc_niezarezerwowana>=szerokosc_potrzebna) then
  begin
    {usunieta instrukcja szerokosc_potrzebna:=-1}
    V(czekajacy_uzytkownik); 
  end;
  V(ochrona);
end;

W tej implementacji semafor czekajacy_uzytkownik może zostać podniesiony dwa (a nawet więcej) razy. Dzieje się tak dlatego, że proces, który budzi proces czekający na semaforze czekajacy_uzytkownik powinien zapewnić, że inny proces zwracający pasmo nie będzie już drugi raz podnosił semafora czekajacy_uzytkownik, a tego nie robi. W rozwiązaniu poprawnym jest to zapewnione gdyż proces, który jako pierwszy budzi proces czekający na semaforze czekajacy_uzytkownik ustawia szerokosc_potrzebna na -1 i tym samym powoduje, że nikt inny nie będzie budził czekającego na semaforze czekajacy_uzytkownik procesu.

Rozwiązanie poprawne korzystające z metody Przekazywania (Dziedziczenia) Sekcji Krytycznej

Problem obecny w niepoprawnym rozwiązaniu, może być naprawiony także w inny sposób. Można do tego wykorzystać technikę nazywaną Przekazywanie (Dziedziczenie) Sekcji Krytycznej. W tym przypadku będzie to polegało na zagwarantowaniu, że po procesie budzącym będzie się wykonywał proces budzony (typ samym zapewniamy że przed procesem budzonym nie będzie się wykonywał żeaden proces zwalniający szerokość pasma).

var
  szerokosc_niezarezerwowana: integer := N;
  szerokosc_potrzebna: integer := -1;
  rozpatrywanie_rezerwacji_pojedynczo: binary semaphore := 1;
  czekajacy_uzytkownik: binary semaphore := 0;
  ochrona: binary semaphore := 1;

procedure rezerwacja_pasma(szerokosc_dla_uzytkownika: integer)
begin
  P(rozpatrywanie_rezerwacji_pojedynczo);
  P(ochrona);
  if (szerokosc_dla_uzytkownika<szerokosc_niezarezerwowana) then
    begin
      szerokosc_potrzebna := szerokosc_dla_uzytkownika;
      V(ochrona);
      P(czekajacy_uzytkownik);
      {korzystanie z przekazanej ochrona}
      szerokosc_potrzebna:=-1;
    end;
  szerokosc_niezarezerwowana := szerokosc_niezarezerwowana - szerokosc_dla_uzytkownika;
  V(ochrona);
  V(rozpatrywanie_rezerwacji_pojedynczo); 
end;

procedure zwalnianie_pasma(szerokosc_dla_uzytkownika: integer)
begin
  P(ochrona);
  szerokosc_niezarezerwowana := szerokosc_niezarezerwowana + szerokosc_dla_uzytkownika;
  if (szerokosc_potrzebna<>-1 and szerokosc_niezarezerwowana>=szerokosc_potrzebna) then
    V(czekajacy_uzytkownik); {przekazywanie ochrony}
  else
    V(ochrona); 
end;