Programowanie współbieżne i rozproszone/PWR Wykład 4

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania

Komunikacja synchroniczna

Przypomnijmy podstawowe cechy komunikacji synchronicznej. Procesy synchronizują ze sobą: nadawca czeka z wysłaniem do momentu aż odbiorca będzie gotowy do odbioru i na odwrót: odbiorca jest wstrzymywany na instrukcji odbioru do chwili aż sterowanie w nadawcy dojdzie do instrukcji wysłania.

Czasem trudno jest jednak wprowadzić jednoznaczne rozgraniczenie na proces nadający i odbierający, gdyż w czasie pojedynczej synchronizacji procesów może dojść do wymiany komunikatów w obie strony. Tak właśnie dzieje się to w Adzie.

Spotkania (randki) w Adzie

Mechanizm synchronizacyjny udostępniany przez Adę to tzw. spotkania albo randki. W spotkaniu uczestniczą dwa (lub więcej) procesy, które w Adzie noszą nazwę zadań. Zacznijmy od omówienia randki między dwoma zadaniami.

Zadanie aktywne i pasywne

Spośród zadań uczestniczących w randce jedno jest zadaniem aktywnym a drugie zadaniem pasywnym. Zadanie aktywne inicjuje randkę, ale w czasie jej trwania nie robi nic. Zadanie pasywne jest zadaniem, które udostępnia pewne wejścia. Wejścia te mogą być wywoływane przez zadanie aktywne, które chce zainicjować randkę. Gdy dojdzie do randki, zadanie pasywne zajmuje się jej obsługą, wykonując określony fragment programu, podczas gdy zadanie aktywne jest wstrzymywane w oczekiwaniu na zakończenie randki.

Zadania w Adzie składają się z dwóch części. Pierwsza część to tzw. specyfikacja zadania. Specyfikacja określa jakie wejścia są udostępniane przez zadanie i jakie są ich argumenty. Właściwa treść zadania jest określona w osobnym fragmencie kodu.

Zacznijmy zatem od specyfikacji zadania.

Wejścia

Każde zadanie może udostępniać na zewnątrz pewne wejścia. Są to jakby "procedury", które mogą być wywołane przez inne zadania i realizowane "w ich imieniu" przez zadanie bierne. Każde wejście ma nazwę i, jeśli jest taka potrzeba, parametry. Te same zasady dotyczą zresztą procedur. Każdy parametr w Adzie specyfikuje się podając jego identyfikator, typ oraz sposób przekazania. Są trzy sposoby przekazywania parametrów w Adzie:

  1. Parametry wejściowe to parametry określane za pomocą słowa kluczowego in. Ich wartości mogą być w trakcie spotkania (lub wewnątrz procedury/funkcji) odczytywane, ale nie można nic na nie zapisać. W szczególności nie mogą więc występować po lewej stronie instrukcji przypisania.
  2. Parametry wyjściowe są określane za pomocą słowa kluczowego out. Można na nie zapisywać wewnątrz procedury, ale ich wartości początkowe są nieokreślone.
  3. Parametry wejściowo-wyjściowe specyfikowane za pomocą słowa kluczowego inout. Są one połączeniem parametrów wejściowych i wyjściowych. Są one odpowiednikiem parametrów przekazywanych przez zmienną z języka Pascal.

Kierunek przekazywania parametrów jest zawsze określany z punktu widzenia specyfikowanego procesu. Przypuśćmy na przykład, że chcemy rozwiązać problem producentów i konsumentów. Chcemy aby producenci umieszczali wyprodukowane dane w buforze, skąd pobierają je konsumenci. Pierwsze pytanie, na jakie musimy odpowiedzieć to jak zaimplementować bufor? Zastosujemy dość standardową technikę w modelu rozproszonym wprowadzając dodatkowy proces. Proces ten o nazwie Bufor będzie procesem biernym zarówno przy komunikacji z producentem jak i konsumentem. Wynika to z tego, że bufor nie ma żadnej możliwości stwierdzenia, kiedy przechowywana przez niego informacja będzie potrzebna konsumentowi ani kiedy ma skomunikować się z producentem w celu odebrania od niego kolejnej porcji danych. To producent i konsument są stronami inicjującymi spotkanie.

Wyspecyfikujmy więc zadanie Bufor z dwoma wejściami:

  • Weź do wkładania do niego danych i
  • Daj do pobierania danych.

Odpowiedni fragment kodu wygląda tak:

task Bufor is
  entry Weź (x: in Integer);
  entry Daj (x: out Integer);
end Bufor;

Zauważmy, że parametr wejścia Weź jest wejściowy, bo to wywołujący przekazuje liczbę całkowitą do włożenia do bufora. Analogicznie parametr wejścia Daj jest wyjściowy, bo za jego pomocą proces aktywny dostaje liczbę z bufora.

Semantyka randki

Jak wygląda randka w Adzie? Aby w ogóle do niej doszło proces aktywny musi wywołać pewne wejście udostępniane przez proces pasywny. Przykładowo, konsument wywołuje wejście Daj bufora za pomocą instrukcji:

Bufor.Daj (x)

przy czym x jest pewną zmienną lokalną konsumenta, na której zostanie zapisana odczytana z bufora liczba.

Wywołanie wejścia zadania biernego powoduje wstrzymanie procesu wywołującego aż do chwili, gdy sterowanie w procesie biernym dotrze do specjalnej instrukcji akceptującej. Proces wywołujący jest przy tym wstrzymywany w kolejce związanej z danym wejściem. Gdy sterowanie w zadaniu biernym dochodzi do instrukcji akceptującej --- jej składnię omówimy za chwilę --- dochodzi do randki między zadaniem biernym a zadaniem aktywnym znajdującym się na początku kolejki zadań oczekujących na danym wejściu. Należy od razu zwrócić uwagę, że może zdarzyć się także inny scenariusz. Sterowanie w procesie biernym może dojść do instrukcji akceptującej wejście, zanim zostanie ono wywołane przez jakiekolwiek zadanie aktywne. Jak przystało na model synchroniczny, zadanie bierne jest wtedy również wstrzymywane do chwili wywołania wejścia przez jakieś zadanie.

Sama randka ma następujący przebieg:

  1. Zadanie aktywne przekazuje do zadania biernego wartości wszystkich parametrów wejściowych i wejściowo-wyjściowych.
  2. Zadanie aktywne zostaje wstrzymane do czasu zakończenia randki.
  3. Zadanie bierne wykonuje instrukcje stanowiące treść instrukcji akceptującej.
  4. Po zakończeniu instrukcji akceptującej zadanie bierne przekazuje do zadania aktywnego wartości wszystkich parametrów out oraz inout i przechodzi do kolejnej instrukcji za instrukcją akceptującą.
  5. Po odebraniu wartości parametrów wyjściowych zadanie aktywne wznawia swoje działanie.

Z powyższego opisu wynika, że zadanie aktywne po przekazaniu parametrów do zadania biernego pozostaje bezczynne aż do końca randki. Na taki mechanizm można więc patrzeć jak na zwykłe wywołanie procedury z tym, że procedura jest wykonywana nie przez wywołującego, lecz przez inny proces. Zadanie aktywne to wywołujący, który po wywołaniu czeka aż zadanie bierne wykona treść procedury i przekaże wartości wynikowe i dopiero wznawia wykonanie.

Kluczową instrukcją w opisywanym mechanizmie jest instrukcja akceptująca. Ma ona następującą składnię:

accept <nazwa wejścia> (<parametry>) do
  <ciąg instrukcji>;
end <nazwa wejścia>;

W trakcie spotkania wykonują się wszystkie instrukcje między accept a end. Jeśli ten ciąg jest pusty to można użyć uproszczonej składni pisząc:

accept <nazwa wejścia>;

Oczekiwanie selektywne

Semantyka

Niedeterminizm

Zliczanie oczekujących

Przykłady

Producenci i konsumenci

Pięciu filozofów. Wersja 1.

Pięciu filozofów. Wersja 2.

Select po stronie aktywnej

Spotkania z udziałem wielu procesów

RPC jako implementacja spotkań