programowanie rozproszone

119
Programowanie rozproszone ADA 95

Upload: berny

Post on 12-Jan-2016

44 views

Category:

Documents


0 download

DESCRIPTION

Programowanie rozproszone. ADA 95. Wstęp. Ada 95 jest uniwersalnym j ęz ykiem oprogramowania przeznaczonym do tworzenia oprogramowania dużej skali. Rozszerzenie wersji Ada 83. Tworzenie języka Ada 83 na zlecenie Departamentu Obrony USA. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Programowanie rozproszone

Programowanie rozproszone

ADA 95

Page 2: Programowanie rozproszone

Wstęp

Ada 95 jest uniwersalnym językiem oprogramowania przeznaczonym do tworzenia oprogramowania dużej skali. Rozszerzenie wersji Ada 83.Tworzenie języka Ada 83 na zlecenie Departamentu Obrony USA.Uznana przez ANSI (American National Standards Institute).  

Schemat procesu wytwarzania oprogramowania 

Nieformalny opis problemuAnaliza problemu

Specyfikacja problemuAnaliza wymagań na oprogramowanie

Specyfikacja oprogramowaniaProjekt

Implementacja Oprogramowanie

Wdrażanie i eksploatacja

Page 3: Programowanie rozproszone

Ada jest związana z:

- bezpośrednio z fazą implementacji,

- pośrednio z fazą projektowania (w części język projektowania – obiektowość (języka),

- fazą wdrażania i eksploatacji- łatwość modyfikacji oprogramowania w zależności od zmieniających się wymogów użytkowych.  

Własności języka ADA 95

- obiektowość – usprawnia fazę implementacji, możliwość implementacji fragmentów programu przez niezależne zespoły programistyczne

- mechanizm rozłącznej kompilacji,

- możliwość łączenia programów napisanych w innych językach, własność polimorfizmu statycznego i dynamicznego (Ada 95),

- mechanizmy tworzenia programów równoległych i programów rozproszonych (Ada 95).

Page 4: Programowanie rozproszone

Zastosowania

- asynchroniczne i synchroniczne systemy współbieżne rozproszone

- tworzenie systemów czasu rzeczywistego,

- specyficzną klasą zastosowań są systemy związane z bezpieczeństwem:

- systemy nawigacyjne w lotnictwie,

- systemy sygnalizacyjne w kolejnictwie

- systemy medyczne.

Struktura logiczna programu

Podstawowe jednostki programowe: - podprogramy (procedury i funkcji)

- Pakiety

- jednostki rodzajowe (rodzajowe podprogramy i pakiety)

- Zadania

- obiekty chronione

Page 5: Programowanie rozproszone

Struktura fizyczna programu

Strukturyzacja fizyczna programu wiąże się z potrzebami programowania w dużej skali:

- mechanizmy rozdzielnej kompilacji - wielokrotne używanie komponentów oprogramowania poprzez tworzenie własnych bibliotek

Jednostkami kompilacji mogą być dowolne jednostki programowe. Jednostkami bibliotecznymi mogą być podprogramy (procedury i funkcji), pakiety, jednostki rodzajowe (rodzajowe podprogramy i pakiety)

Jednostki biblioteczne -> potomne jednostki biblioteczneSpecyfikacja i treść jednostek bibliotecznych są oddzielnymi jednostkami kompilacyjnymi. Pomiędzy jednostkami kompilacyjnymi można określać hierarchiczną relacje, która określa wzajemną widzialność jednostek.

Zestaw standartowych pakietów.

Ada 95 w stosunku do wersji Ada 83 wprowadza jeszcze jedną formę strukturyzacji programu – rozproszenie programu. Podział programu na partycje, z których każda może być wykonywana na osobnym komputerze.

Page 6: Programowanie rozproszone

Przykład – pierwszy program

with text_io; use text_io; procedure pierwszy is begin

put_line(“pierwszy program!”); end pierwszy;

-- specyfikacja kontekstu -- procedura główna programu

-- standardowa procedura z

-- pakietu Text_IO

Oznaczenia w specyfikacji składni języka

bold - słowo kluczowe

{ } - dowolny ciąg powtórzeń

[ ] - element opcjonalny

\ \ - wybór jednego z elementów składowych

| - alternatywa

::= - reguła produkcji

... - składnik identyczny

Page 7: Programowanie rozproszone

Identyfikatory

W identyfikatorze małe i duże litery są nierozróżnialne.  Identyfikator := Litera { [ Podkreślnik ] litera | cyfra}  Tablica a b1 a_1 lancuch_1

Liczby

987_657_333 równoważne 987657333

3.14_592

2E5 -- 200000

0.124e-2 -- 0.00125

Literały znakowe i napisowe

‘A’, ‘F’, ‘1’, ”ada 95”, ”lancuch”

Page 8: Programowanie rozproszone

Typy

Typ składa się z dwóch elementów: - skończonego zbioru wartości A1, ..., An

- zbioru funkcji f1, ..., fk

Definicja typu

type Identyfikator_typu is definicja typu type tydzien is (pn, wt, sr, cz, pt, so, ni)

Zawężenie użytkowania typu

type T1 is private type T1 is limited private -- ograniczenie widoku typu – użytkownik nie zna struktury typu

Definicja podtypu

subtype Identyfikator_podtypu is Identyfikator_typu range [zawężenie]

subtype dni_robocze is tydzien range pn .. pt

subtype Litera is Character range ‘a’..’h’

subtype godz is integer range 0..24

Page 9: Programowanie rozproszone

Uwaga: definicja podtypu nie wprowadza nowego typu. Ma to znacznie dla pojęcia zgodności typów.

Dla dowolnego typu lub podtypu są określone dwa atrybuty:

T’Base – dla danego podtypu T zwraca nazwę tego typu bazowego, dla typu T jego nazwę.

T’Size – określa najmniejszą liczbę bitów, wymaganą do przedstawienia wartości typu T.

put( "Najmniejszy integer: " );

put( Integer'First ); new_line;

put( "Najwiekszy integer: " );

put( Integer'Last ); new_line;

put( "Integer (w bitach): " );

put(Integer'Size ); new_line;

Page 10: Programowanie rozproszone

Trzy sposoby definiowania typu:

- zawężenie zbioru wartości zdefiniowanego typu- rozszerzenie zbioru wartości zdefiniowanego typu (tylko typy znakowe)- zdefiniowanie nowego typu jako złożenie wartości typów wcześniej zdefiniowanych. (typy tablicowy i typ rekordowy)

Typ całkowity ze znakiem

Schemat definicji typu jest następujący:

range proste_wyrażenie_statyczne ..proste_wyrażenie_statyczne proste wyrażenie statyczne – to wyrażenie, którego wartość może być obliczona w trakcie kompilacji. proste wyrażenie statyczne musi być wyrażeniem statycznym dowolnego typu całkowitego z zakresu System.Min_Int, System.Max_Int gdzie System.Min_Int, System.Max_Int są stałymi, których wartość zależą od implementacji.W Adzie istnieje predefiniowany podtyp całkowity ze znakiem - Integer

Typ całkowity resztowy

Typ całkowity resztowy jest typem całkowitym bez znaku, w którym jest stosowana arytmetyka resztowa.

Page 11: Programowanie rozproszone

Schemat definicji typu jest następujący:

mod Wyrażenie_statyczne;

Wartość elementu Wyrażenie_statyczne jest nazywana modułem i musi być statyczną dodatnią wartością dowolnego typu całkowitego, nie większą niż wartości określone w pakiecie System. Typ całkowity resztowy oznacza typ całkowity z zakresem bazowym od zera do wartości (moduł-1).Przykłady deklaracji typów całkowitych resztowych :

type Bajt_bez_znaku is mod 8; -- zakresem są liczby 0..7 type Słowo is mod 16; -- zakresem są liczby O .. 15

Jeśli wartości typu resztowego są argumentami binarnego operatora logicznego, to operatorybinarne stosowane do tych wartości traktują je jako ciągi bitów, tzn. wykonuje się operację kolejnona każdej parze bitów np.

A: Bajt_bez_znaku := 6; -- A jest ciągiem 0110 B: Bajt_bez_znaku := 5; -- B jest ciągiem 0101 C, D: Bajt_bez_znaku;C := A or B; -- wartość C będzie równa 7 D : = A and B; -- wartość D będzie równa 4

Page 12: Programowanie rozproszone

Typy rzeczywiste

Typy rzeczywiste są typami pochodnymi pierwotnego typu universal_real. W związku z niemożnością reprezentowania wszystkich wartości zakresu wskazanego w definicji typu rzeczywistego stosuje się zawężenie dokładności, które określa:

- liczbę cyfr dziesiętnych wymaganą do zapisu wartości typu (dokładność względna), lub- parametr delta, który określa różnicę pomiędzy kolejnymi wartościami typu (dokładność  bezwzględna).

Typy zmiennopozycyjne

Schemat definicji typu zmiennopozycyjnego: digits wyrażenie_statyczne [ range proste_wyrażenie_statyczne_1 .. proste_wyrażenie_statyczne_2 ] gdzie:

- wyrażenie_statyczne określa wymaganą dokładność dziesiętną (minimalna liczba znaczących cyfr dziesiętnych). Wartość tego wyrażenia musi być liczbą typu Integer.

- proste_wyrażenie_statyczne_i (i=l,2) powinno przyjmować wartość rzeczywistą.

- dla każdego podtypu zmiennopozycyjnego Z jest definiowany atrybut Z'Digits, podający wymaganą dokładność dziesiętną dla elementów tego typu. Wartość tego atrybutu jest typu universal_integer.

Page 13: Programowanie rozproszone

W Adzie istnieje predefiniowany podtyp zmiennopozycyjny ze znakiem, nazwany Float, deklarowanyw pakiecie Standard.

Przykłady deklaracji typów zmiennopozycyjnych:type Temperatura is digits 3 range 16.4 .. 20.7; -- wartości będą zawierały 3 cyfry --dziesiętnych z podanego zakresu

type Real is digits 8; -- obliczenia dla wartości tego typu -- będą prowadzone z dokładnością -- do 8-miu cyfr dziesiętnych

subtype Zakres is Real range 0.0 .. 3.0; -- wartości typu Zakres należą do -- przedziału [0.0,3.0]

Typy stałopozycyjne

typy stałopozycyjne:mają określoną stałą dokładność reprezentacji liczby. Jest to wartość absolutna, nazywana parametrem delta typu stałopozycyjnego.zbiór wartości typu stałopozycyjnego składa się z liczb będących całkowitą wielokrotnością liczbynazywanej ziarnem typu.

Page 14: Programowanie rozproszone

Przykłady deklaracji typów stałopozycyjnych:

type Pomiar is delta 0.125 range 0.0 .. 125.0; -- wartości typu Pomiar będą -- zapisywane co 0.125 -- w podanym zakresie

type Pieniądze is delta 0.01 digits 9; -- pozwala zapisać spotykane -- zlotowe kwoty pieniężne' -- z dogodnością do groszy

Page 15: Programowanie rozproszone

Typy tablicowe

Składowe typu są jednoznacznie identyfikowane przez jeden lub więcej wartości indeksów należącychdo podanych typów dyskretnych.Typ tablicowy może mieć zawężenie indeksów, co oznacza, że górne i dolne granice każdego z indeksów są określone. Element takiego typu jest nazywany tablicą zawężoną, a schemat deklaracji takiego typu tablicowego przyjmuje postać:

type Nazwa typu tablicowego is array (Wskazanie_podtypu _dyskretnego I Zakres {Wskazanie podtypu _dyskretnego l Zakres}) of [aliased] Wskazanie_podtypu

Można definiować także typ tablicowy nie zawężony, w którym granice indeksów nie są określone. Granice te wprowadza się deklarując obiekt (tablicę) tego typu (podaje się zawężenie indeksu lubwartości początkowe składowych tablicy).Schemat definicji dla typu tablicowego nie zawężonego jest następujący:

type Nazwa typu tablicowego is array (Oznaczenie Podtypu range <> {,Oznaczenie Podtypu range <>}) of [aliased] Wskazanie Podtypu

przy czym Oznaczenie_podtypu powinno wskazywać typ dyskretny.

Page 16: Programowanie rozproszone

Typy predefiniowane - pakiet Standard type Boolean is (False, True) -- predefiniowane operacje =, /=, <, >, <=, >=, and, or, xor, not   type Integer is zdefiniowany przez implementacje subtype Natural is Integer range 0 .. Integer’Last; subtype Positive is Integer range 1 .. Integer’Last; -- predefiniowane operacje dwuargumentowe =, /=, <, >, <=, >=, abs, +, -, *, /, **      -- predefiniowane operacje jednoargumentowe +, -

Spotkania w Adzie

Komunikacja w Adzie jest: 

SYNCHRONICZNA NIEBUFOROWANA

DWUKIERUNKOWA 

Page 17: Programowanie rozproszone

Zadanie wywołujące Zadanie przyjmujące

       

Zadanie wywołujące przyjmujące musi znać: - nazwę zadania przyjmującego - nazwę miejsca spotkania

Zadanie przyjmujące nie zna nazwy zadania wywołującego.

Wejście 1

Wejście 2

Wejście 3

Wejście 4

Zadanie 1

Zadanie 2

Page 18: Programowanie rozproszone

Zaleta modelu asymetrycznego – łatwość programowania serwerów i nie musi być zmieniana treść zadania serwera podczas dodawania nowego zadania wywołującego.

Zadanie składa się z:specyfikacji –

zawierać może jedynie deklaracje wejść, każde zadanie musi posiadać specyfikacje nawet jeśli jest ona pustej

treści.Przykład specyfikacji :

task Bufor is entry Wstaw (I : in integer);

entry Pobierz (I : in integer)end Bufor;

BuforWstaw(I); task body Bufor is begin

........select

accept Wstaw (I : in integer) do --instrukcje

end Wstaw;accept Sygnalizuj;

............ end Bufor;

Wywołanie wejścia przez zadanie:

Page 19: Programowanie rozproszone

Semantyka spotkania jest następująca:

Zadanie wywołujące przekazuje swoje parametry wejściowe (in) do zadania przyjmującego i zostaje zawieszone do momentu zakończenia spotkania.

Zadanie przyjmujące wykonuje instrukcje z treści accept

Parametry wyjściowe (out) są przekazywane do zadania wywołującego

Spotkanie jest teraz zakończone i żaden z procesów nie jest już wstrzymywany.

with Ada.Text_IO; use Ada.Text_IO;procedure pierwsza isczyja_kolej : Integer:=1;  Task P1;task body P1 is begin

loopPut_line("sekcja lokalna zadania 2");

loop exit when czyja_kolej =1; end loop;Put_line("sekcja krytyczna zadania 1");

 czyja_kolej:=2;

end loop;end P1;

Page 20: Programowanie rozproszone

Task P2;task body P2 is begin

loopPut_line("sekcja lokalna zadania 2");

loop exit when czyja_kolej =2; end loop;Put_line("sekcja krytyczna zadania 2");

 czyja_kolej:=1;

end loop;end P2; begin null;end Pierwsza; package pakiet_z_semaforami is

task type Semafor_binarny isentry Czekaj;entry Sygnalizuj;

end Semafor_binarny; task type semafor is

entry Inicjalizuj(N : in Integer);entry Czekaj;entry Sygnalizuj;

end Semafor; end pakiet_z_semaforami; /////////////////////////////////////////////////////////////////////

Page 21: Programowanie rozproszone

package body pakiet_z_semaforami istask body Semafor_binarny isbegin

loop select

accept Czekaj;accept Sygnalizuj;

or terminate; end select;

end loop;end Semafor_binarny; task body semafor is

licznik : integer;begin

accept Inicjalizuj(N : in Integer) dolicznik:=N;

end Inicjalizuj;  loop

select when Licznik >0 =>

accept Czekaj do licznik:=licznik-1;end Czekaj;

or accept Sygnalizuj do licznik:=licznik+1; end Sygnalizuj;

or terminate;

end select;end loop;

end Semafor;end pakiet_z_semaforami; ////////////////////////////////////////////////////////////

Page 22: Programowanie rozproszone

with Ada.Text_IO; use Ada.Text_IO; with pakiet_z_semaforami; use pakiet_z_semaforami; procedure wzajemne_wykluczanie is

s: Semafor_binarny;  task Z1;task body Z1 is begin

loopPut_line("sekcja lokalna zadania 1");S.Czekaj;Put_line("sekcja krytyczna zadania 1");S.sygnalizuj;

end loop;end z1; task z2;task body z2 is begin

loopPut_line("sekcja lokalna zadania 2");S.Czekaj;Put_line("sekcja krytyczna zadania 2");S.sygnalizuj;

end loop;end z2; begin null;end wzajemne_wykluczanie; 

Page 23: Programowanie rozproszone

Synchronizacja trzech i więcej spotkań Instrukcja zagnieżdżona accept

task body Z1 is begin ................

accept Synch_2 do -- Z2 wywołuje to wejście accept Synch_3 -- Z3 wywołuje to wejście

end Synch_2;..................end Z1;

Select w zadaniu wywołującym  Zadanie wywołujące może się zawiesić jedynie czekając na jedno wejście.

task body Z is begin loop select procesX.wejscie(... );

ordelay 1.0 ; ------sekunda ciąg instrukcji;

end select; end loop;end Z;

Page 24: Programowanie rozproszone

Jeśli wywołanie nie będzie przyjęte w zadanym czasie, to próba spotkania będzie zaniechana i wykonają się instrukcje po delay.Ankietowanie jednego lub wielu serwerów :

task body Z is begin loop select serwer_1.wej(...);else null; end select; select serwer_2.wej(...);else null; end select; ............ end loop;end Z;

Ankietowanie co najwyżej jednego serwera :task body Z is begin loop select serwer_1.wej(...);else select serwer_2.wej(...); else end select; end select; end loop;end Z;

Page 25: Programowanie rozproszone

Dynamiczne tworzenie zadań  deklaracja typu zadaniowego, tworzenie za pomocą typu struktury danych zawierającej zadania,

task type typ_bufora is entry Wstaw (I : in integer);

entry Pobierz (I : out integer)end typ_bufora; Bufory : array(1..10) of typ_bufora; procedure P (buf : in typ_bufora) isI : Integer;begin

buf.Wstaw(I); -- wywołanie wejścia z parametremend P;Bufory(I+1).Wstaw(E) -- bezpośrednie wywołanie wejścia P(Bufory(J)); -- parametr będący zadaniem

Priorytety i rodziny wejść  pragma priority(N)  N – literał całkowity 

Page 26: Programowanie rozproszone

Priorytety wpływają na szeregowanie zadań.Nie mają wpływu na wybór gałęzi w instrukcji select.Jeśli jest kilka zadań o tym samym priorytecie to wybór jest losowy. Implementacja musi używać zarządcy z wywłaszczeniem, – jeśli zakończy się okres zawieszenia zadania o wysokim priorytecie, to zarządca musi na jego korzyść przerwać zadanie o niższym priorytecie.  Rodzina wejść:

type Priorytety is (niski, średni, wysoki); task Serwer is entry Żądanie(Priorytety) (.....);end Serwer; task body Serwer is begin loop select accept Żądanie(wysoki) (...) ....;or when Żądanie(wysoki) ‘ Count = 0 => --liczba zadań czekających w kolejceaccept Żądanie(średni) (...) ....;or when Żądanie(wysoki) ‘ Count = 0 => Żądanie(średni ) ‘ Count = 0 =>accept Żądanie(niski) (...) ....; end select;end loop;end Serwer;

Page 27: Programowanie rozproszone

Jeśli rodzina wejść jest duża.   type Priorytety is range 1..100;  for I in Priorytety loop select accept Żądanie(I) (...) ....; else

null; end select; end loop;

Page 28: Programowanie rozproszone

Podprogramy

Podprogramy są podstawowymi jednostkami programowymi Ady. W Adzie istnieją dwa rodzaje podprogramów: procedury i funkcje. Procedury określają działania, zmierzające do uzyskaniażądanego efektu. Funkcje określają działania, zmierzające do obliczenia pewnej wartości.

Podprogramy — pojęcia podstawowe

Definicja podprogramu może składać się z dwóch części: deklaracji podprogramu, opisującej jego interfejs oraz treści, opisującej działanie podprogramu.[specyfikacja _ podprogramu;] -- deklaracja podprogramu specyfikacja _podprogramu is -- treść podprogramu; [ część deklaracyjna] beginciąg_ instrukcji end;

Specyfikacja opisuje interfejs podprogramu, tj. określa rodzaj podprogramu(procedura, funkcja), jegonazwę oraz parametry podprogramu. Specyfikacja funkcji dodatkowa wskazuje nazwę typu wartościobliczanej przez funkcję.Deklaracja oraz treść podprogramu muszą wystąpić w tej samej części deklaracyjnej jednostki otaczającej dany podprogram (wyjątek stanowią podprogramy deklarowane w obrębie pakietów, zadań, obiektów chronionych).

Page 29: Programowanie rozproszone

Funkcje

Treść funkcji ma postać:

function Nazwa funkcji[(Parametry_formalne)] return Nazwa_podtypu is -- specyfikacja [Część_deklaracyjna] begin . . . -- ciąg instrukcji return Wyrażenie; end |Nazwa funkcji];  Deklaracja parametrów funkcji ma postać:  Dok_1 ; |Dok_2;. . . ; Dok_N

Gdzie Dok_i wygląda następująco:

Nazwa: (in) Typ

Jeżeli parametry są tego samego typu, dopuszczalny jest zapis:Narwal, Nazwa_2, ..., Nazwa_K: [in] Typ

W treści funkcji może wystąpić wiele instrukcji return (np. wewnątrz instrukcji wyboru). Wykonaniedowolnej z nich kończy działanie funkcji.

Page 30: Programowanie rozproszone

Przykładem treści funkcji jest:

function Rok przestępny(Rok: Positive) return Boolean is - Funkcja Rok przestępny ma jeden parametr formalny o nazwie Rok. - Funkcja zwraca wartość True, jeżeli rok jest rokiem przestępnym oraz - wartość False w przeciwnym przypadku

begin if Rok mod 4 /= O then return False; elsif Rok mod 100 /= O then return True; elsif Rok mod 400 /= O then return False; else return True; end if; end Rok _ przestępny; 

Treść podprogramu (w tym również funkcji) może być poprzedzona deklaracją.  Deklaracja funkcji ma postać: function Nazwa funkcji [(Parametry formalne)]

return Nazwa podtypu;

Page 31: Programowanie rozproszone

Wywołanie funkcji

Przykład 1 : Wynik: Boolean; . . . Wynik:= Rok__przestepny (1996) ; -- zmienna Wynik przyjmie wartość True Przykład 2 : function Silnia (N: Integer) return Integer is begin if N=0 then return l ; else return N * Silnia(N - l); end if; end Silnia; . . .

S: Integer;

. . .

S := Silnia (5); -- wartość zmiennej S, po wykonaniu funkcji Silnia. -- będzie równa 120

Page 32: Programowanie rozproszone

Formalne parametry funkcji mogą być dowolnego ,nazwanego typu, również nie zawężonego .Gdytyp parametru jest typem nie zawężonym, zakres tego parametru jest ustalony na podstawiezawężonej wartości parametru aktualnego, np.

Type Wektor is array ( Integer range <>) of Integeer; -- nie zawężona tablica liczb . . .

function Suma (W: Wektor) return Integer is S: intager : = 0;Begin for j in W'Rangę loop -- J przyjmuje kolejne wartości z zakresu S := S + W (J) ; -- indeksów tablicy W ( atrybut Rangę ) end loop; return S; end Suma; . . .

W: Wektor (1. .3) := (1 ,2 ,3) ; S: Positive; . . .

S := suma(W) ; -- wartość S, po wykonaniu funkcji Suma, -- będzie równa 6 

Page 33: Programowanie rozproszone

Dany podprogram może zostać przemianowany, tzn. można mu nadać dodatkowo nową nazwę, obowiązującą w danym kontekście. Nowa nazwa nie przesłania poprzedniej, pozwala jedynie łatwiejkorzystać z podprogramu. Ogólny schemat przemianowania funkcji jest następujący:

function Nowa_nazwa_funkcji[(Parametry_formalne)] return Nazwa podtypu renames Poprzednia nazwa funkcji;

Poniżej umieszczono przykład definicji funkcji uzyskanej w wyniku przemianowania: function Nowa_suma(W : Wektor) return Integer renames Suma;

Wywołanie funkcji Nowa_suma (W) obliczy taką samą wartość jak wywołanie funkcjiSuma (W).

Page 34: Programowanie rozproszone

Operatory

Operatory są jedno lub dwuargumentowymi wbudowanymi funkcjami języka .W odróżnieniu od innych Funkcji, operatory mogą być używane w notacji wrostkowej i przedrostkowej. Oznacza to, żezapis X+1 jest równoważny wywołaniu funkcji "+" (X, l). W notacji przedrostkowej symbol operatoramusi być ujęty w cudzysłów.

Programista może używać symboli operatorów do nazywania własnych funkcji.

Poniżej przedstawiono przykład definicji operatora "+" dla dodawania wektorów:

type Wektor is array (Integer rangę o) of Float;function "+" (L, R: Wektor) return Wektor is W: Wektor (L' Rangę) ; -- tablica o dynamicznie ustalanym rozmiarze! begin for I in L' Range loop W(I) :=L(I) +R(I); -- operator "+" oznacza dodawanie liczb -- typu Floatend loop;return W; end " + " ;L Wektor(1..3) := (1.0, 2.0, 3.0);R Wektor(1..3) := (-1.0, -2.0, -3.0); W Wektor (l. . 3) ; -- operator "+" jest używany do dodawania wektorówL ;= L + R; -- po Wykonaniu instrukcji, L będzie wektorem zerowym W ;="+"( L, R); -- alternatywne użycie operatora

Page 35: Programowanie rozproszone

Deklaracja własnych operatorów jest jednym z przykładów przeciążania podprogramów.Operator "/=", który zwraca wartość Boolean, może być zdefiniowany jedynie przez definicjękomplementarnego operatora "=", tzn. zdefiniowanie operatora "=" oznaczają również niejawne zdefiniowanie operatora "/=".

Procedury

Treść procedury tworzy się według schematu:

procedure Nazwa_procedury[(Parametry_formalne)] -- specyfikacja procedury is [Część_deklaracyjna] begin ... --ciąg instrukcji, opisujący działanie procedury [return;]end [Nazwa_procedury);

Deklaracja parametrów ma postać:  Dok_1; Dok_2;. . .; Dok_N; Gdzie Dok_1 wygląda następująco :Nazwa: (Tryb) TypJeżeli parametry są tego samego typu, to dopuszczalny jest zapis:Nazwa_1, Nazwa_2, ..., Nazwa_K: (Tryb} Typ

Page 36: Programowanie rozproszone

Każdy parametr formalny może wystąpić w jednym z trzech tzw. trybów przekazywania parametrówin, in out, out. Tryb przekazywania parametru oznacza kierunek przekazania danych podczas wywołania podprogramu.W przypadku , gdy tryb przekazywania parametrów nie jest ustalony jawnie, tryb in przyjmuje się za domyślny.W treści procedury może znaleźć się bezparametrowa instrukcja return. Jej wykonanie kończy wykonanie procedury, po czym sterowanie jest przekazane do jednostki, która wywołała procedurę.

Przykład:

procedure Zamień (X, Y; in out Float) is Tmp: Float; begin Tmp := X; X := Y; Y := Tmp ; end Zamień ;

Deklaracja procedury jest tworzona według schematu: procedure Nazwa_procedury [(Parametry_formalne)] ;Deklaracja procedury Zamień ma zatem postać: procedure Zamień(X, Y: in out Float);Wywołanie procedury jest instrukcją o postaci: Nazwa_procedury [(Parametry_aktualne)];Poniżej pokazano przykład wywołania procedury Zamień: A: Float := 1.0; B: Float := -1.0; . . . Zamień (A, B) ; -- po wykonaniu procedury wartość zmiennej A= -1.0, B = 1. 0

Page 37: Programowanie rozproszone

Przekazywanie parametrów do podprogramów

Parametry aktualne mogą być przekazywane do podprogramu przez położenie lub przez nazwę.Przekazywanie parametrów przez położenie polega na ustawieniu parametrów aktualnych w listą deklaracji parametrów formalnych, np. : Function Suma (A, B : Integer) return Integer;. . . W, S: Integer;. . .W : = Suma (5 ,6) ; -- parametr formalny A zostanie powiązany z wartością 5, -- a parametr B z wartością 6 S : = W ; W : = Suma (S ,6); -- parametr A zostanie powiązany z wartością zmiennej S, -- a parametr B z wartością 2

Przekazywanie parametrów przez nazwę polega na tym, że parametr aktualny jest poprzedzonynazwą powiązanego z nim parametru formalnego oraz symbolem "=>", np.: S : = Suma(A=>5, B=>6) ; -- parametr A zostanie powiązany z wartością 5, -- a parametr B z wartością 6 S : = Suma(A=>W, B=>2) ; -- parametr A zostanie powiązany z wartością -- zmiennej W, a parametr B z wartością 2 S : = Suma(B=>6, A=>5) ; S : = Suma(B=>2, A=>W) ;

Page 38: Programowanie rozproszone

Przykład:procedure P(X: Integer; Y, Z: in out Float); A: Float := 2.0; B: Float := -3.5; P(3, Z=>A, Y=>B);

Dla parametrów przekazywanych w trybie in można określić domyślne wartości początkowe (analogicznie do początkowych wartości deklarowanych zmiennych). Para metry, dla których określono wartości domyślne, mogą zostać opuszczone w wywołaniu podprogramu. Można jednak w zwykły sposób zmienić ich wartości. W definicjach operatorów (np. "+") nie mogą wystąpić deklaracje parametrów domyślnych.

Przykład:

function Suma(A: Float := 3.14; B: Float := 0.0) return Float;

W: Float; . . .

W = Suma; -- parametr A przyjmie wartość 3.14, B wartość 0.0

W = Suma(2.0); -- parametr A przyjmie wartość 2.0, B wartość domyślną 0.0

W = Suma(B=>2.0); -- parametr A przyjmie wartość domyślną 3.14, -- B nową wartość 2.0

Page 39: Programowanie rozproszone

Przeciążanie podprogramów

Użytkownik może zdefiniować wiele podprogramów o tej samej nazwie i różnym działaniu.Jeśli różne podprogramy mają takie same nazwy i różne parametry lub w przypadku funkcji różne typy wyników, mamy do czynienia z przeciążaniem podprogramów. nazywanym również statycznym polimorfizmem.Jeśli podprogramy mają identyczne nazwy, typy parametrów i wyniku (w przypadku funkcji) mówimy o przesłanianiu podprogramów.Wybór przeciążonego podprogramu jest dokonywany statycznie, na podstawie typów. parametrów aktualnych. W przypadku funkcji, których specyfikacje różnią się jedynie typem zwracanego wyniku, o wyborze podprogramu decyduje kontekst wywołania.

Przykładem przeciążania podprogramów jest:

type Dni_tygodnia is (pn, wt, sr, cz, pt, sb, nd) ; type Dni robocze is (pn, wt, sr, cz, pt);procedure Oblicz(D: Dni_tygodnia); -- 1) procedura Obliczprocedure Oblicz (D: Dni_robocze) ; -- 2) przeciążona procedura Oblicz

Wywołania procedury Oblicz mogą być następujące:

Oblicz(sb); -- wywołanie procedury 1) Oblicz (Dni robocze ' (wt) ); -- wywołanie procedury 2)

Kwalifikacja typu parametru aktualnego w drugim wywołaniu procedury Oblicz jest niezbędna w celu ustalenia, którą z przeciążonych procedur wywołać (tu zostanie wywołana procedura oznaczona 2).

Page 40: Programowanie rozproszone

Ilustracją jest przykład przeciążenia operatora”+” dla typów pochodnych;

type T1 is new integer;type T2 new integer; . . .A1 T1;B1 T2; . . .A := A + 1; -- przeciążony “ +” dla T1B := B + 1; -- przeciążony “ +” dla T2A := B; -- niepoprawne przypisanie (T1 jest innym typem, niż T2) 

Podprogramy rodzajowe

Każdy podprogram może mieć dwa rodzaje parametrów — parametry zwykłe, takie jak w Pascalu, czy C, wykorzystywane w trakcie wykonania programu, deklarowane za nazwą podprogramu, oraz parametry rodzajowe, wykorzystywane na etapie kompilacji.Podprogramy z parametrami rodzajowymi nazywa się podprogramami rodzajowymi.Parametry rodzajowe zwiększają możliwość wielokrotnego użycia tego samego podprogramu w różnych kontekstach. Podprogram rodzajowy pełni rolę wzorca, na podstawie którego, w czasie kompilacji, są konkretyzowane różne, zależne od parametrów, deklaracje danego podprogramu.

Definicja programu rodzajowego składa się z deklaracji oraz treści.

Deklaracja podprogramu rodzajowego ma postaćgeneric -- deklaracja podprogramu rodzajowego [Formalne_parametry_rodzajowe] Specyfikacja_podprogramu_rodzajowego;

Page 41: Programowanie rozproszone

Schemat składni, opisujący treść podprogramu rodzajowego jest identyczny jak dla zwykłego podprogramu.

Formalnymi parametrami rodzajowymi podprogramu mogą być:• obiekty,• typy.• podprogramy.

Poniżej podano przykład definicji podprogramu rodzajowego uzyskanego przez dodanie parametru rodzajowego do procedury Zamień:

generic type Typ rodź is private; -- deklaracja parametrówprocedurę Zamień(X, Y: in out Typ_rodz); -- rodzajowych procedurę Zamień(X, Y: in out Typ_rodz) is -- specyfikacja Tmp: Typ__rodz ; -- podprogramu Begin -- rodzajowego Tmp := X; -- treść podprogramu X := Y; -- rodzajowego Y := Tmp; end Zamień;

Page 42: Programowanie rozproszone

Aby wykorzystać procedurę Zamień, należy ją ukonkretnić. Konkretyzacja podprogramu parametryzowanego obiektem i (lub) typem ma postać:

procedurę Nazwa__podprogramu ukonkretnionego is new Nazwa_podprogramu_rodzajowego[(Parametry_aktualne)] ;

Aktualne parametry rodzajowe mogą być kojarzone z parametrami formalnymi zarówno" przez położenie jak i przez nazwę.

Przykładowe konkretyzacje procedury Zamień mogą być następujące:

procedure Z1 is new Zamień(Integer); --konkretyzacja procedury typem Integer

procedure Z2 is new Zamień(Float); -- konkretyzacja procedury typem Float

procedure Z3 is new Zamień(Typ_rodz => Moj_typ) ; -- konkretyzacja procedury Zamień typem Mój typ

Obiekty rodzajowe mogą być wykorzystywane do przekazania wartości, czy zmiennej do lub z podprogramu rodzajowego. Deklaracja obiektów, będących parametrami rodzajowymi podprogramu ma postać:

Nazwa_l, Nazwa_2, ..., Nazwa_N : [Tryb] Typ [:= Wartość_domyślna] ;

Page 43: Programowanie rozproszone

Wyjątki Zgłoszenie wyjątku powoduje zaniechanie (przerwanie) normalnego wykonania programu Programista może jednak zdefiniować pewne akcje (obsługą wyjątku), które mają być wykonane jakoreakcja na wystąpienie danego wyjątku. Niektóre wyjątki są predefiniowane w języku, inne mogą być deklarowane przez programistę. Wyjątki predefiniowane zgłaszane są automatycznie, jeżeli nastąpi naruszenie zasad określonych przez semantykę języka, np. gdy zostanie przekroczony zakres wartości danego typu. Wyjątki zadeklarowane w programie zgłaszane są instrukcją raise w warunkach określonych przez programistę.

Wyjątki predefiniowane W Adzie istnieją cztery predefiniowane wyjątki (zadeklarowane w pakiecie standard): Constraint Error, Program Error, Storage Error i Tasking_Error .

Wyjątek Constraint_Error jest zgłaszany, m.in., gdy:• nastąpi wywołanie podprogramu, w którym wartość aktualna parametru typu wskaźnikowego (przekazywanego w trybie in lub in out) jest równa null,• drugi parametr operacji /, mod, rem jest równy zero,• wartość indeksu tablicy przekracza zakres dopuszczalnych wartości,• wartość skalarna przekroczy zakres bazowy swojego typu.Wyjątek Program_Error jest zgłaszany, m.in., gdy:• w programie następuje odwołanie do niedostępnego bytu lub widoku,• wywoływany jest podprogram lub wejście obiektu chronionego, którego deklaracja nie została jeszcze opracowana,• wykonanie funkcji nie zakończyło się instrukcją return.

Page 44: Programowanie rozproszone

Wyjątek Storage_Error jest zgłaszany wtedy, gdy brakuje pamięci do opracowania deklaracji lub wykonania instrukcji języka (np. do obliczenia alokatora).Wyjątek Tasking_Error jest zgłaszany wtedy, gdy komunikacja z jakimś zadaniem jest niemożliwa (zadanie zostało już usunięte).Programista ma możliwość wyłączenia niektórych predefiniowanych sprawdzeń wykonywanych w czasie wykonania programu za pomocą pragmy Suppress Użycie pragmy Suppress poprawia efektywność wykonania programu (eliminuje dodatkowe czynności sprawdzające — kod wynikowy jest krótszy), przerzuca jednak odpowiedzialność za poprawność programu na programistę.

Deklarowanie i zgłaszanie wyjątków

Deklaracja wyjątków ma postać:

Nazwa _wyjątku l. Nazwa _wyjątku 2,. . . , Nazwa _wyjątku N.; Exception; Przykładami deklaracji wyjątków są:

Blad: exception;

Package Stos is Przepełnienie_bufora: exception; Begin . . . end Stos; Blad_odczytu, Blad_zapisu: exception;

Page 45: Programowanie rozproszone

Przemianowania wyjątku Przepełnienie _bufora zadeklarowanego pierwotnie w pakiecie o nazwie Stos można dokonać następująco:

Declare use Stos Przepełnienie: exception renames Stos.Przepełnienie Bufora; Begin . . . end;

Wyjątki zadeklarowane przez użytkownika są zgłaszane instrukcją raise, po której występuje nazwa wyjątku. Programista określa, jakie warunki muszą zostać spełnione, aby dany wyjątek został zgłoszony. Poniżej umieszczono przykład zgłoszenia wyjątku Blad przy próbie dzielenia przez zero:

procedure Dzielenie(Dzielna, Dzielnik: Float; Wynik: out Float) is Bląd: exception; begin if Dzielnik =0.0 then raise Błąd; -- zgłoszenie wyjątku Błąd, gdy Dzielnik jest równy 0.0 else Wynik := Dzielna/Dzielnik; end if; end Dzielenie;

Page 46: Programowanie rozproszone

Obsługa wyjątków

Wykonanie instrukcji lub opracowanie deklaracji może spowodować zgłoszenie wyjątku Po zgłoszeniu wyjątku normalne wykonywanie programu jest zaniechane, a sterowanie jest przekazywane do zdefiniowanej przez programistę strefy obsługi wyjątków. Strefa obsługi wyjątków może wystąpić w dowolnym bloku instrukcji, w zasięgu deklami |i danego wyjątku. Swoje strefy obsługi wyjątków mogą mieć: podprogramy, treści pakietów, instrukcje bloku, treści zadań, treści wejść w obiektach chronionych i instrukcje accept. Strefę obsługi umieszcza się zawsze na końcu bloku, po ostatniej normalnie wykonywanej instrukcji. Ilustruje to poniższy schemat:

Instrukcja [exception -- strefa obsługi wyjątków Segment_obslugi_wyjątków l Segment_obsługi_wyjątków_N ]

Każda strefa obsługi wyjątków musi zawierać co najmniej jeden segment obsługi wyjątków. Segment obsługi wyjątków ma postać:

when Nazwa_wyjątku => Instrukcja (...} lub when Nazwa wyjątku l l Nazwa wyjątku 2 | ... i Nazwa wyjątku N Instrukcja {...}

Page 47: Programowanie rozproszone

Nazwy wyjątków umieszczone w jednej strefie obsługi wyjątków muszą być unikatowe. Zbiory wyjątków należących do różnych segmentów muszą być rozłączne.Ostatni segment obsługi wyjątków może zawierać kluczowe słowo others, które oznaczą wszystkie wyjątki zgłoszone w danym bloku, lecz nie wymienione w poprzednich segmentach obsługi, włącznie z wyjątkami nie nazwanymi w bieżącym kontekście:

when others => Instrukcja {...)

W segmencie obsługi others obsłużone zostaną wyjątki: • predefiniowane, • nazwane w danym kontekście, lecz nie wymienione w innych segmentach obsługi wyjątków, • wyjątki propagowane z jednostek dynamicznie zagnieżdżonych w bloku, w którym wystąpił segment obsługi others

Obsługę wyjątku można wykorzystać do przywrócenia stanu systemu (o ile to możliwe) do stanu sprzed pojawienia się wyjątku lub do zapobiegania skutkom wyjątku.Na przykład obsługa wyjątku Blad dla nieco zmodyfikowanej procedury Dzielenie musiałaby być następująca:

Page 48: Programowanie rozproszone

procedure Dzielenie(Dzielna, Dzielnik: Float; Wynik:out float; OK: in out Boolean) is -- zakłada się. że parametrowi aktualnemu, podstawianemu za parametr formalny OK -- będzie przypisana wartość True przed wywołaniem procedury Dzielenie Błąd: exception; Begin . . . raise Błąd; exception when Błąd => OK := Faise; end Dzielenie; X, Y, W: Float; OK: Boolean := True; . . . Dzielenie(X, Y, W, OK) ; if OK then ... -- wynik W obliczony poprawnie

Po wykonaniu segmentu obsługi wyjątku, sterowanie przekazywane jest na koniec bloku, który obsłużył dany wyjątek.

Page 49: Programowanie rozproszone

Przykład:

procedurę Dzielenie(Dzielna, Dzielnik: Float; Wynik:out Float; OK: in out Boolean) is -- zakłada się, że parametrowi aktualnemu, podstawianemu za parametr formalny OK -- będzie przypisana wartość T'rue przed wywołaniem procedury Dzieleniebegin Wynik ; " Dzielenia/dzielnik;exception when Constraint_Error => ok.:= false;end dzielenie;

W przypadku, gdyby w procedurze Dzielenie nie wystąpiła strefa obsługi wyjątków, wyjątek Constraint_Error byłby propagowany do jednostki, w której ta procedura została wywołana. 

Page 50: Programowanie rozproszone

Propagacja wyjątków

W przypadku zgłoszenia wyjątku sterowanie jest przekazywane do strefy zawierającej obsługi wyjątku, zadeklarowanego w bloku, którego wykonanie spowodowało pojawienie się wyjątku. Jeżeli w danym bloku nie ma segmentu obsługi zgłoszonego wyjątku lub jeżeli w czasie wykonania segmentu obsługi zgłoszony zostanie inny wyjątek, to następuje automatyczne zgłoszenie wyjątku w bloku dynamicznie otaczającym blok zawierający wyjątek. Zgłoszenie danego wyjątku w nowym kontekście nazywa się propagacją wyjątku. Oznacza to np., że wyjątek nie obsłużony w danymprogramie, będzie propagowany do jednostki, w której ten podprogram był wywołany

Propagacja wyjątku trwa tak długo, aż zostanie znaleziony odpowiedni segment obsługa wyjątku. Jeżeli poszukiwanie segmentu obsługi nie powiedzie się, wykonanie programu zostanie przerwane z komunikatem o błędzie (np. nie obsłużony wyjątek Program Error).Wyjątki zgłoszone i nie obsłużone w treści zadania nie podlegają propagacji.Zgłoszenie wyjątku może nastąpić nie tylko podczas wykonywania instrukcji, lecz także podczas opracowywania deklaracji. W tym przypadku przyjmuje się zasadę propagacji wyjątku do jednostki dynamicznie otaczającej deklarację, której opracowanie nie po wiodło się.

Programista może, w ramach obsługi danego wyjątku, wymusić jego propagację. Do propagacji wyjątku służy bezparametrowa instrukcja raise. Instrukcja ta pozwala obsługiwać ten sam wyjątek na wielu poziomach, tzn. przez wiele dynamicznie zagnieżdżonych jednostek programowych.

Page 51: Programowanie rozproszone

Poniżej przedstawiono przykład wykorzystania instrukcji raise:

procedure Główna is X, Y: exception;procedure P is begin. . .raise X; . . . raise Y; exception when X | Y => . . . -- Pierwotna obsługa wyjątków X i Y przez procedure P raise; -- Wymuszenie propagacji wyjątków X, Y end P; procedurę Q is begin P; exception when X |Y -> . . . – Wtórna (dodatkowa)obsługa wyjątków X i Y przez Q end Q; begin Q; end Głowna;

Page 52: Programowanie rozproszone

Typy wskaźnikowe ograniczone Ograniczenie typu wskaźnikowego polega na jego powiązaniu z określonym (przez implementacje), dynamicznym obszarem pamięci (ang.storage pool), odpowiednikiem sterty w innych językach programowania. W obszarze tym są przechowywane, utworzone przez alokatory, obiekty danego typu wskaźnikowego. Wartości ograniczonego typu wskaźnikowego mogą wskazywać jedynie na elementy związanego z nim dynamicznego obszaru pamięci.

Deklaracja ograniczonego typu wskaźnikowego wygląda następująco:

type Nazwa_typu_wakaźnikoweqo is access Wskazanie_podtypu;

Przykładem deklaracji ograniczonego typu wskaźnikowego jest:

type Wsk_Int is access Integer; -- zmienne typu Wsk_Int wskazują na -- obiekty zawierające liczby całkowite

Przy definiowaniu struktury danych złożonej z rekordów (np. listy), w której jeden z elementów rekordu będzie wskazywał na rekord tego samego typu, deklaracja typu wskazywanego jest dwuetapowa. Spowodowane jest to koniecznością zadeklarowania każdego identyfikatora przed jegoużyciem. Pierwsza deklaracja (częściowa) stwierdza, że identyfikator Element_listy jest nazwą typu. Druga deklaracja określa wymaganą, pełną definicję typu:

type Element listy; -- częściowa deklaracja typu Element listytype Wsk na element listy is access Element listy; -- deklaracja typu wskaźnikowegotype Element listy is -- pełna deklaracja typu Element listy recordDana: Integer; Następnik: Wsk na element listy;end record;

Page 53: Programowanie rozproszone

Utworzenie obiektu wskazywanego przez zmienną wskaźnikową wymaga użycia alokatora, który zwraca adres tworzonego obiektu. Dla zadeklarowanej zmiennej:

Zmienna wskaźnikowa: Nazwa typu wskaźnikowego;

użycie alokatora może być następujące:

Zmienna wskaźnikowa : = new Wskazanie_podtypu['(Początkowe_wartości_wskazywane) ] ;

Zmienne typu wskaźnikowego mogą przyjąć wartość null oznaczającą, że zmienna nic wskazuje na żaden obiekt. Po inicjacji, zmienne wskaźnikowe przyjmują wartość null, o ile programista od razu nie określi wskazywanej wartości. Przykładami deklaracji zmiennych wskaźnikowych są:

N: Wsk Int; -- zmienna N ma wartość null L: Wsk Int : = new Integer'(0); -- zmienna wskazuje na liczba 0 P: Wsk_Int : = new Integer; -- zmienna P wskazuje mi liczbę -- o nieokreślonej wartości

Przykładem deklaracji listy jest:

Eleml, Elem2: Element_listy;El, E2 : Wsk_na_element_listy; -- zmienne El, E2 mają wartość Null . . .E3: Wsk_na_element_listy := new element_listy’(1, null); E4: Wsk_na_element_listy := new element_listy’(Elem1);

Page 54: Programowanie rozproszone

Zmienna E3 wskazuje na record, rekord którego składowe są zainicjowane odpowiednio wartościami l i null, Zmienna E4 wskazuje na rekord o wartościach określonych przez Elem1.

Dla typów prostych notacja: Nazwa zmiennej wskaźnikowej.all

oznacza wskazywaną wartość, np. zapis N.all:= 2;

oznacza, że wartość zmiennej wskaźnikowej N nie ulega zmianie, zmienia się jedynie wartość wskazywana przez N.Zachodzi istotna różnica między wartościami typu wskaźnikowego i wartościami przez nie wskazywanymi. Przypisanie:

Elem2 := Eleml;

spowoduje skopiowanie wartości składowych rekordu Eleml do składowych rekordu Elem2, natomiast przypisanie:

El := E2;

spowoduje, że zmienna El będzie wskazywać na ten sam rekord, co zmienna E2 (w pamięci jest tylko jeden rekord, na który wskazują obie zmienne).

Instrukcja przypisania służy utworzeniu kopii wartości rekordu wskazywanego:El.all := E2.all;

Page 55: Programowanie rozproszone

Typy wskaźnikowe ogólne

Typy wskaźnikowe, które mogą wskazywać na zadeklarowane obiekty oraz podprogramy, nazywane są ogólnymi typami wskaźnikowymi. W tym podrozdziale omawia się typy wskaźnikowe, wskazujące na stale i zmienne Ady. Deklaracja ogólnego typu wskaźnikowego ma postać:

type Nazwa_ogólnego_ typu_wskaźnikowego is access constant Wskazanie_podtypu; lubtype Nazwa_ogólnego_ typu_wskaźnikowego is access all Wskazanie_podtypu;

W deklaracji ogólnego typu wskaźnikowego musi wystąpić specyfikacja modyfikatora dostępu— słowo all lub constant. Użycie modyfikatora all oznacza, że wskazywany obiekt może być czytany i modyfikowany za pośrednictwem zmiennej wskaźnikowej. Użycie modyfikatora constant oznacza, że za pomocą zmiennej wskaźnikowej można jedynie odczytywać wskazywane obiekty.

Przykładami deklaracji ogólnych typów wskaźnikowych są:

type Wsk_na_stale_Integer is access constant Integer; -- Wartości typu Wsk na stale Integer wskazują na stale typu Integer type Wsk na_zmienne Integer is access all Integer; -- Wartości typu Wsk_na_zmienne_Integer wskazują na zmienne typu Integer type Wsk na op is access all Operacja_Binarna'Ciass; -- Wartości typu Wsk na op wskazują na zmienne należące do typu klasowego -- Operacja Binarna

Page 56: Programowanie rozproszone

Zmienne wskaźnikowe mogą wskazywać na zadeklarowane obiekty pod warunkiem, że obiekty te są deklarowane jako obiekty aliasowane. Wartości wskaźników ustala się za pomocą atrybutów Access lub Unchecked_Access. Na użycie atrybutu Access nałożone są pewne ograniczenia. Mianowicie, atrybutu Access nie można użyć do ustalania wartości zmiennej wskaźnikowej, której typ ma szerszy zasięg, niż zasięg wskazywanego obiektu. Użycie atrybutu Unchecked_Access nie podlega tym ograniczeniom— programista odpowiada za jego poprawne użycie.

Użycie atrybutów Access oraz Unchecked_Access może być następujące:

type Wsk_na_stala_Integer is access constant Integer;CPI, CPC: Wsk_na_stala_Integer;I: aliased Integer; -- słowo kluczowe aliased C: aliased constant Integer := 1815; . . .

CPI : = I ‘Access -- odczyt wskaźnika na aliasowaną zmienną I CPC: = C ‘Access -- odczyt wskaźnika na aliasowaną stałą C type Wsk_na_zmienna_integer is access all Integer;IP : Wsk_na_zmienna_integer;I : aliased integer;. . .

IP := I'Unchecked_Access; -- odczyt wskaźnika na zmienną II : =I+1; -- zmiana wartości zmiennej I 

Page 57: Programowanie rozproszone

Do ustalenia wartości wskazywanych przez zmienne ogólnego typu wskaźnikowego można również używać alokatorów tak samo, jak dla typów wskaźnikowych ograniczonych.

Ciekawym zastosowaniem ogólnego typu wskaźnikowego jest możliwość zaprogramowania tablicy o wierszach różnej długości:

package Uslugi_komunikacyjne istype Typ kodu wiadomości is rangę 0..100;subtype Wiadomość is String;function Daj_wiadomosc(Kod_wiadomosci: Typ_kodu_wiadomosci) return Wiadomość;pragma Inline (Daj wiadomość) ; -- treść funkcji Daj_wiadomosc zostanie wstawiona podczas kompilacji -- w miejsce jej wywołań end Usługi komunikacyjne;

package body Uslugi_komunikacyjne istype Obsługa wiadomości is access constant Wiadomość; -- Typ wskaźnikowy, wskazujący na obiekty typu Wiadomość aliasowane stałe -- typu WiadomośćWiadomosc_0: aliased constant Wiadomość := "OK"; Wiadomość _l: aliased constant Wiadomość := "Włącz"; Wiadomość _2: aliased constant Wiadomość := "Wyłącz"; . . .Tablica_wiadomosci: array (Typ_kodu_wiadomosci) of Obsługa wiadomości : = --Ustalenie wartości elementów tablicy (wskaźników na kolejne Wiadomości) (O => Wiadomosc_0'Access,1 => Wiadomość _l'Access,2 => Wiadomosc_2'Access, . . . etc. ) ;

Page 58: Programowanie rozproszone

function Daj_wiadomosc(Kod_wiadomosci: Typ_kodu_wiadomosci) return Wiadomość is begin return Tablica wiadomości(Kod wiadomości).all; end Daj wiadomość; end Usługi komunikacyjne;

Aby otrzymać wiadomość o określonym kodzie (O — "OK", l — "Włącz" itd.) wystarczy wywołać z odpowiednim parametrem (kodem) funkcję Daj_wiadomość pakietu Uslugi_komunikacyjne.

Pakiety

Pakiety są podstawową jednostką strukturalizacji programów w Adzie. Pakiet jako podstawowa jednostka projektowania programu oznacza, że jest on jednostką dekompozycji programu, która ma służyć do podziału funkcji całego programu na części składowe. Pakiet jest kolekcją pewnych bytów programowych, które są dostępne według ścisłe określonych zasad. Przez inne jednostki programowe pakiet jest postrzegany jako jednostka świadcząca im pewne usługi.Pakiet jest też jednostką, która może być oddzielnie kompilowana, uruchamiana i testowana. Gotowy pakiet może być jednostką składową nie tylko programu, dla którego go pierwotnie opracowano, ale także – jako jednostka biblioteczna — może być używany wielokrotnie do budowy innych programów.

Page 59: Programowanie rozproszone

Struktura pakietu

Definicja pakietu składa się z dwóch jednostek składniowych: specyfikacji pakietu oraz treści pakietu. Podział na dwie części wiąże się z postulatem separacji specyfikacji usług od ich implementacji.

Składnia specyfikacji przedstawia się następująco:

package Nazwa_pakietu is . . . -- deklaracje publiczne [private . . . ] -- deklaracje prywatne end [[Nazwa_jednostki_macierzystej.]Nazwa_pakietu]; 

Specyfikacja pakietu określa usługi oferowane przez pakiet innym jednostkom programowym.W pierwszej części — publicznej — zestawione są deklaracje bytów (stałe, zmienne. typy, podprogramy, zadania, pakiety, wyjątki, przemianowania, klauzule reprezentacji) które mogą być używane przez inne jednostki programowe (użytkowników pakietu).W drugiej części — prywatnej — są zestawione deklaracje bytów, z których użytkownik nie może korzystać bezpośrednio. Informacja o tych bytach jest potrzebna podczas, kompilacji programu. Byty prywatne są używane wyłącznie w treści pakietu. Czesi prywatna jest opcjonalna.

Page 60: Programowanie rozproszone

Przykładem specyfikacji pakietu jest:

package Liczby wymierne is type Wymierna is recordLicznik: Integer; Mianownik: Positive; end record;function "/" (X, Y:Integer) return Wymierna;-- konstrukcja liczby wymiernejfunction "=" (X, Y: Wymierna) return Boolean;function "+"(X: Wymierna) return Wymierna;-- operacja jednoargumentowa, przeciążonafunction "-"(X: Wymierna) return Wymierna;-- operacja jednoargumentowa, przeciążonatunction "i"(X, Y; Wymierna) return Wymierna;-- operacja dwuargumentowa, przeciążonafunction „=” (X ,Y: Wymierna) return Wymierna;-- operacja dwuargumentowa przeciążonafunction „*” (X ,Y: Wymierna) return wymierna;;-- operacja dwuargumentowa przeciążonafunction „/” (X ,Y: Wymierna) return wymierna;;-- operacja dwuargumentowa przeciążona przekroczenie_zakresu : exception;end liczby_wymierne;

Page 61: Programowanie rozproszone

Pakiet Liczby Wymierne oferuje deklarację typu o nazwie Wymierna, który jest reprezentacją liczb wymiernych, oraz zestaw podstawowych działań (operacji) na liczbach wymiernych, wraz z wyjątkiem sygnalizującym ewentualne przekroczenie zakresu wartości,

Składnia części implementacyjnej, czyli treści pakietu ma postać:

package body Nazwa_pakietu is. . . -- deklaracje [begin. . . ] -- ciąg instrukcjiend [[Nazwa_jednostki_macierzystej.]Nazwa_pakietu] ;

Szkielet przykładowej implementacji pakietu Liczby_Wymierne jest następujący:

package body Liczby_wymierne isprocedure Wspolny_mianownik(X, Y; in out Wymierna) is Mianowniki Positive;function NWW(x, y: Positive) return Positive is -- NWW - najmniejsza wspólna wielokrotność, begin . . . end; begin Mianownik := NWW(X.Mianownik, Y.Mianownik); X := (X.Licznik * Integer(Mianownik)/Integer(X.Mianownik), Mianownik) ; -- konwersja typu Mianownik na typ Integer Y := (Y.Licznik * Integer(Mianownik)/Integer(Y.Mianownik), Mianownik); end Wspólny mianownik;

Page 62: Programowanie rozproszone

function "/"(X, Y: Integer) return Wymierna is begin if Y = O then raise Przekroczenie_zakresu; end if; return(X, Positive(Y)); end; function "="(X, Y: Wymierna) return Boolean is begin ... end; function "+" (X: Wymierna) return Wymierna Is begin ... end; function "-" (X: Wymierna) return Wymierna is begin ... end; function "+"(X, Y: Wymierna) return Wymierna is begin ... end; function "-" (X, Y: Wymierna) return Wymierna is begin ... end; function " * "(X, Y: Wymierna) return Wymierna is begin ... end; function "/"(X, Y: Wymierna) retum Wymierna is begin ... end;end Liczby wymierne;

Page 63: Programowanie rozproszone

Przykładowy szkielet procedur:

Przykład1

procedure Obliczanie_pierwiastkow_rownania(...) ispackage Liczby wymierne is -- bezpośrednia deklaracja pakietu . . . end Liczby wymierne;package body Liczby wymierne is . . .end Liczby_wymierne;begin. . .end Obliczanie_pierwiastków_równania;

Przykład 2

with Liczby_wymierne -- specyfikacja kontekstuprocedure Obliczenie_pierwiastków_równania (. . .) is . . . begin . . .end Obliczenie_pierwiastków_równania;

Page 64: Programowanie rozproszone

Typy prywatne

Ważnym mechanizmem systematyzującym projektowanie oprogramowania jest mechanizm abstrakcyjnych typów danych. Abstrakcyjny typ danych jest tu rozumiany jako zbiór (lub zbiory) wartości oraz związany z nim zbiór operacji. Istotą tego powiązania jest to, ze na wartościach ustalonego zbioru (lub zbiorów) można wykonywać tylko ustalone operacje.Pakiety wraz z typami prywatnymi stanowią mechanizm pozwalający na definiowanie abstrakcyjnych typów danych. Możliwość tę rozpatrzmy na przykładzie pakietu definiującego stos, na którym gromadzi się liczby całkowite.

package Stos_liczb_calkowitych istype Stos is private; -- niepełna definicja typu Stosprocedure Dopisz(S: in out Stos; X: in Integer) ;procedure Zdejmij(S: in out Stos; X: out Integer);function "="(S, T: Stos) return Boolean;Pusty, Przepełniony : exception; private Max; constant Integer := 100;type Tablica is array Integer rangę <> of Integer;typa Stos isrecord -- pełna definicja typu Stos S: Tablica (l. .Max) ; -- zaważenie tablicy Szczyt : Integer range O . . Max := 0;end recordend Stos_liczb_całkowitych;

Page 65: Programowanie rozproszone

Treść przykładowego pakietu przedstawia się następująco:

package body Stos_liczb_całkowitych isprocedure Dopisz(S: in. out Stos; X: in Integer) is begin if S.Szczyt = Max then raise Przepełniony; end if ; S.Szczyt := S.Szczyt + l; S.S(S.Szczyt) := X;end Dopisz;procedure Zdejmij(S: in out Stos; X: out Integer) is begin if S.Szczyt = 0 then raise Pusty; end if ; X := S.S (S.Szczyt) ; S.Szczyt : = S.Szczyt - l;end Zdejmij;function „=” (S, T: Stos) return isbegin if S.Szczyt /- T.Szczyt then return False; end if; for I in 1 . .S.Szczyt loop if S.S(I) /- T.S(I) then return False; end if;end loop;return True;end "-";end Stos_liczb_całkowitych;

Page 66: Programowanie rozproszone

Przykład:

declareuse Stos_liczb_calkowitych; Sl, S2: Stos; -- zmienne reprezentujące wartości dwóch różnych stosów X : Integer;begin Dopisz(Sl, 10); -- operacja dopisania elementu na pierwszy stos Dopisz (Sl, 20); -- operacja dopisania elementu na pierwszy stos . . . Zdejmij (S1, X); -- operacja zdjęcia elementu z pierwszego stosu . . . Dopisz (S2, 100); -- operacja dopisania elementu na drugi stos . . . if S1 = S2; -- operacja porównania stosów then . . . else S1 : = S2; -- operacja przypisania stosów . . . end if;end;

Page 67: Programowanie rozproszone

Przykład: package Modyfikacja_stosu is type Zmod_stos is private; -- niepełna definicja typu Pusty: constant Zmod_stos; -- tu nie ma możliwości przypisania -- wartości stałej Pusty private Max: constant Integer : = 100; type Tablica is array (Integer range <>) of Integer; type Zmod_stos is record -- pełna definicja typu S: Tabllica (1..Max); Szczyt : Integer range 0. . Max ; end record;Pusty: constant Zmod_stos : = (S => (others -> 0), Szczyt => 0); -- nadanie wartości stałej odroczonej Pusty

end Modyfikacja stosu;

Ograniczone typy prywatne

Ograniczenie sposobu użytkowania bytów publicznych pakietu można jeszcze wzmocnić przez wykorzystanie konstrukcji ograniczonego typu prywatnego. Deklaracja ograniczonego typu prywatnego T ma postać:

type T is limited private;

Istota ograniczenia związanego z typem T polega na tym, że użytkownik pakietu nie może na zmiennych typu T wykonywać przypisywania wartości, czyli instrukcji przypisania, ani też porównywania ich wartości, czyli wykonywania operacji = oraz /=.

type Stos is limited private;

Page 68: Programowanie rozproszone

Struktura programów

Przykładem zawierania jednostek programowych (pakietów) jest:

package Zewnetrzny is . . . package Wewnetrzny is -- jednostka programowa zagnieżdżona w jednostce . . . – Zewnetrzny procedure Srodek ; -- jednostka programowa zagnieżdżona w jednostce . . . – Wewnętrzny i jednostce Zewnetrzny end Wewnetrzny; . . . procedure Wypisz; -- jednostka programowa zagnieżdżona w jednostce . . . – Zewnetrznyend Zewnetrzny;

Pakiet Zewnetrzny zawiera, zagnieżdżone w nim trzy jednostki programowe: pakiet Wewnetrzny, procedurę Środek (zawartą w pakiecie Wewnętrzny) oraz procedurę Wypisz.

Page 69: Programowanie rozproszone

Jednostki kompilacji i jednostki biblioteczne

Jednostką kompilacji jest jednostka programowa oddzielnie przedstawiona do kompilacji. Jednostką kompilacji może być: pakiet, podprogram, obiekt chroniony lub jednostka rodzajowa, ale nie może być zadanie. Oddzielnie mogą być kompilowane specyfikacja i treść pakietu oraz obiektu chronionego.

Przemianowania

Rozbudowa programu, korzystanie z nazw bytów pochodzących z innych jednostek powoduje, że stosowane nazwy bytów mogą siać się nieczytelne. Ada pozwala nadać bytowi nowi| nazwie, przy zachowaniu wszystkich jego własności. Operację nadania nowej na/wy bytowi określa sil; przemianowaniem.

Przykładem użycia przemianowanych bytów jest następująca procedura Główna:

package body Stos is Max : constant : = 30; P: array(l..Max) of Integer; Top : Integer range 0..Max;procedure Odłóż(X: Integer) is begin . . . end Odłóż;function Pobierz return Integer is begin . . . end Pobierz;

Page 70: Programowanie rozproszone

begin Góra := 0;end Stos; with Stos;procedure Główna isK, L: Integer;procedure Połóż (X: Integer) renames Stos.Odłóż;function Weź return Integer renames Stos.Pobierz; begin . . . Połóż (K) ; . . . L : =Wez; end Główna;

Przemianowanie pozwala uniknąć konfliktu nazw bytów pochodzących z różnych jednostek, a wynikających z zastosowania klauzuli use. Przemianowanie może również zastąpić stosowanie notacji kropkowej.

Przemianowanie może być stosowane w przypadku operatorów, np.;

function Dodaj (X, Y: Integer) return integer renames "+"; lub function "+" (X, Y: Integer) return Integer renames Dodaj;

Page 71: Programowanie rozproszone

Przemianowanie podprogramów musi być zgodne co do liczby, typów i trybów parametrów oraz typuwyniku w przypadku funkcji. Zgodność nie dotyczy nazw parametrów. Dzięki przemianowaniu można wprowadzać, zmieniać lub usuwać parametry i wyrażenia domyślne. Można przemianować obiekt typu złożonego, przy powtarzających się wyliczeniach wartości tego obiektu, np. niech będą dane deklaracje:

type Data urodzenia is record Dzień: Integer range l..31; Miesiąc: Integer range l..12; Rok: Integer ;end record;

type Osoba is record Imię: String; Nazwisko: String; Urodzony: Data urodzenia;end record;

type Baza osób is array(1..20) of Osoba; Dane: Baza osób;

Page 72: Programowanie rozproszone

Rozpatrzmy fragment programu:

for I in Dane'Range loopPut (Dane (I).Urodzony.Dzień);Put ("-");Put (Dane(I).Urodzony.Miesiąc) ;Put ("-");Put (Dane (I).Urodzony.Rok);Put (" r.");end loop;

i ten sam fragment programu z wykorzystaniem przemianowania:

for I in Dane'Range loop declare Data: Data urodzenia renames Dane(I).Urodzony; begin Put (Data.Dzień) ; Put ("-"); Put (Data,Miesiąc.) ; Put ("-"); Put (Data.Rok); Put (" r.") , end;end loop; 

Page 73: Programowanie rozproszone

Mechanizmy programowania obiektowego

Przyjmuje się, że język programowania jest językiem obiektowym jeżeli:

• umożliwia deklarowanie typów obiektów, • umożliwia deklarowanie operacji na obiektach, • umożliwia enkapsulację (hermetyzację) obiektu i jego operacji, • udostępnia mechanizm dziedziczenia, • udostępnia mechanizm polimorfizmu statycznego i dynamicznego.

W języku Ada obiektem jest stała lub zmienna . Obiekt przyjmuje wartości ustalonego typu i powstaje przez deklaracje lub przez użycie alokatora.Dla każdego typu mogą być definiowane elementarne operacje. Zazwyczaj, inne języki programowania obiektowego w definicji typu (klasy) zawierają tylko jego (jej) atrybuty oraz operacje na atrybutach. Ada nie wymusza tak ścisłej hermetyzacji (enkapsulacji)Enkapsulacja jest możliwa dzięki pakietom. Wykorzystując strukturę pakietu można zdefiniować w nim typ rekordowy określając jego atrybuty oraz elementarne operacje tego typu. Ponieważ, w ramach tego samego pakietu można zdefiniować również inne operacje, .nie związane z danym typem, stąd brak w Adzie ścisłej enkapsulacji. 

Page 74: Programowanie rozproszone

Przykładem deklaracji typu w Adzie jest:

package Baza istype Osoba is tagged record Nazwisko: String (l..30); Imię: String (l..30) ;end record;  function Zmień_nazwisko (dane: in Osoba) return Osoba; -- operacja elementarna typu Osobafunction Podaj_nazwisko (dane: in Osoba) return String (l..30); -- operacja elementarna typu Osoba procedure Komunikat; -- operacja me związana z typem Osoba end Baza;

W przykładzie została zdefiniowana specyfikacja pakietu Baza zawierająca deklarację znakowanego typu rekordowego Osoba zawierająca dwa atrybuty Nazwisko i Imię. W specyfikacji pakietu Baza zostały zadeklarowane operacje elementarne typu Osoba, w postaci funkcji Zmien_nazwisko i Podaj_nazwisko oraz procedura Komunikat, która nie jest elementarną operacją typu Osoba. Możliwość zadeklarowania procedury Komunikat oraz innych procedur i funkcji, które nie są operacjami elementarnymi typu Osoba oznacza, że specyfikacja pakietu nie zapewnia ścisłej hermetyzacji.

Obiektowe mechanizmy Ady są różne od podobnych mechanizmów występujących w innych językach obiektowych, jak np. C++ czy Smalltalk.

Page 75: Programowanie rozproszone

Dziedziczenie typów

Obiektowe języki programowania zawierają mechanizm definiowania nowego typu na bazie istniejącego. Nowy typ nazywany jest typem pochodnym lub potomnym, natomiast typ, z którego powstał typ pochodny nazywany jest typem bazowym. Pojęcie pochodności typu należy odróżnićod pojęcia pochodności jednostki kompilacji opisanej poprzednim rozdziale. Pochodność typu jest związana z opisem poniżej dziedziczeniem typów.

Typ pochodny może być typem bazowym dla kolejnego typu pochodnego.

Jeśli typ T2 jest typem pochodnym typu T1a typ T3 jest typem pochodnym typu T2, to typ 1 jest typem bazowym dla typu T2. a dla typu T3 typ T1 jest jego pośrednim poprzednikiem. Jeśli typ T4 jest typem pochodnym typu T2, to T2 jest typem bazowym typu T4, a typ t l jest jego pośrednim poprzednikiem.

Typy T1, T2, T3 i T4 tworzą hierarchię, którą można przedstawić w postaci drzewa. Typ T1 jest korzeniem drzewa, którego gałęziami są typy T2, T3 i T4. Typ T2 jest korzeniem poddrzewa, które zawiera dwie gałęzie — typy T3 i T4.

T2

T1

T3 T4

Page 76: Programowanie rozproszone

Typ pochodny dziedziczy strukturę i operacje elementarne typu bazowego. Mechanizm dziedziczenia uważa się za pełny, jeśli umożliwia:

• tworzenie typu pochodnego w oparciu o typ bazowy, • przeciążanie operacji typu bazowego, • rozszerzanie typu bazowego.

Przy braku którejś z dwóch ostatnich możliwości mówi się o dziedziczeniu ograniczonym.

Język Ada zapewnia dwa pierwsze mechanizmy dla dowolnego typu. Trzeci mechanizm jest możliwy tylko dla typu znakowanego.

Typem znakowanym może być tylko typ rekordowy. Każdy typ pochodny od typu znakowanego jest z definicji typem znakowanym (nawet bez wyróżnienia jego definicji słowem kluczowym tagged), np.:

type Punkt is tagged -- bazowy typ znakowyrecord Wspolrzedna_X: Float; Współrzędna Y: Float;end record;type Okrąg is new Punkt with -- typ pochodnyrecord -- (również znakowy) promień: Float; -- dodana składowaend record;type Punkt_ Plaszczyzny is new Punkt -- typ pochodny (znakowany)with null record; -- bez nowych składowych

Page 77: Programowanie rozproszone

Zmiana struktury typu pochodnego może spowodować zmianę implementacji operacji odziedziczonych przez ten typ.

Jeśli przyjmiemy, że dla typu Punkt została zdefiniowana operacja Pole:

function Pole (Ob: in Punkt) return. Float is begin return 0.0;end Pole;

to zostanie ona odziedziczona przez typy: Okrąg i Punkt_Plaszczyzny. Jednak, dla typu Okrągimplementacja operacji Pole może mieć postać:

function Poie(Ko: in Okrąg) return Float is begin return Pi*Ko.Promień**2;end Pole;

Funkcja Pole została przeciążona, tzn. w zależności od typu parametru (Punkt lub Okrąg) zostanie wyliczona odpowiednia wartość.Niech typ T1 będzie poprzednikiem typu T2. Istnieje możliwość przypisywania wartości obiektów typu T2 do obiektów typu T1 i odwrotnie. W tym celu należy dokonać odpowiedniej (zawężającej lub rozszerzającej) konwersji typów obiektów.

Przy konwersji zawężającej następuje odrzucenie wartości składowych, które nie występują w typie poprzednika. Natomiast przy konwersji rozszerzającej należy podać wartości nowych składowych w kolejności ich deklarowania lub zastosować agregat.

Page 78: Programowanie rozproszone

Przykładowo, mając zadeklarowane zmienne:

X: Punkt := (1.5, 0.7);K: Okrąg := (0.0, 0.0, 50.0);P: Punkt_plaszczyzny;

możemy dokonywać konwersji:

X : = Punkt (K) ; -- wartość składowej K. Promień . . . -- będzie zignorowana K : = (X with 100.5) -- określenie wartości brakującej składowej . . . -- w zmiennej X P : = (X with null record) ; -- przekształcenie do rekordu bez dodatkowych -- składowych

Do konwersji typów można stosować agregat, np.:

K := (X with Promień => 100.5);

Ogólnie, przed słowem kluczowym with może wystąpić dowolne wyrażenie, wskazujące na obiekt typu poprzednika. 

Page 79: Programowanie rozproszone

Klasy i polimorfizm

Polimorfizm dynamiczny zapewniają tylko typy znakowane. Z typem znakowanym są związane dwa pojęcia: klasy typów i typu klasowego.Deklaracja dowolnego typu znakowego powoduje automatyczne, niejawne utworzenie klasy oraz deklaracje typu klasowego.

Nazwa typu klasowego jest określana niejawnie przez atrybut class.

Przykładem użycia typu klasowego jest:

type Opis i s tagged -- typ znakowany record . . . end record; Obiekt_Klasowy : Opis'Class; -- Opis ' Class jest nazwą typu klasowego -- generowanego przez typ Opis

Zmienna obiekt_Klasowy jest typu klasowego o nazwie Opis'class,jej wartościami są wartości typu Opis oraz wartości wszystkich typów pochodnych od typu Opis.Każda wartość typu klasowego ma ukryty znacznik, określający do którego z typów, w danej klasie typów znakowanych, należy ta wartość .Przy deklaracji obiektu typu klasowego jest konieczne jego zainicjowanie. Inicjacja konkretyzuje jeden z typów należący do odpowiedniej klasy. Informacja o dokonanej konkretyzacji (o wybranym typie) jest przechowywana w ukrytym znaczniku obiektu. Konieczność inicjowania wynika z potrzeb implementacji — musi być określony obszar pamięci przydzielany obiektowi typu klasowego.

Page 80: Programowanie rozproszone

Typ klasowy może być typem parametrów formalnych podprogramów. Dzięki temu podprogram może być wywołany dla dowolnych obiektów typu klasowego, czyli parametr aktualny może być dowolnego typu z danej klasy typów, np.:

with Nowy System Sygnałów;procedure Obsłuż sygnały(S: in out Sygnał"Cłass) is. . . begin. . .Zarejestruj (S) ; -- powiązanie zgodne z typem wskazanym w znaczniku . . .end Obsłuż sygnały;

W procedurze Obsluz_sygnaly parametrem aktualnym może być dowolny obiekt należący do klasy typu Sygnał. Natomiast dla procedury Zarejestruj musi być wybrana konkretna jej implementacja zależna od typu parametru S. Wybór tej implementacji nastąpi w trakcie wykonywania programu, a dokładniej, wykonania procedury Obsłuż sygnały, i będzie zależał od typu aktualnego parametru S, przekazanego w ukrytym znaczniku, np.:

Page 81: Programowanie rozproszone

with Nowy_System_Sygnałów; procedure Główna is S1 : Sygnał_l ; S2 : Sygnal_2; S3 : Sygnal_3; SSpec; Sygnał Zagrożenia;Begin . . . Obsluz_sygnaly(Sl); . . . Obsluz_sygnaly(S2) ; . . . Obsłuż sygnaly(S3); . . . Obsluz_sygnaly(SSpec) ;

end Główna;

Każde z wywołań procedury Obsluz_sygnaly jest poprawne. W zależności od typu parametru aktualnego, przy wykonywaniu procedury Obsluz_sygnaly zostanie wykorzystana odpowiednia implementacja procedury Zarejestruj. Będą to kolejno implementacje dla typów: Sygnal_l, Sygnal_2, Sygnal_3 i Sygnal_Zagrozenia.

Przykład jest ilustracją mechanizmu polimorfizmu dynamicznego.

Page 82: Programowanie rozproszone

Istnieje również możliwość zdefiniowania typu wskaźnikowego, wskazującego na obiekty typu klasowego.

Rozpatrzmy przykład oparty, na wcześniej rozpatrywanym, systemie sygnałów. Niech każdy nie obsłużony sygnał trafia do jedynej w systemie kolejki sygnałów. Kolejka ta jest heterogeniczna, tzn. mogą do niej trafiać sygnały różnych typów. Aby obsłużyć tę kolejkę i sygnały w niej zawarte możemy zastosować następujące rozwiązanie;

type Wskaźnik sygnału is access Sygnał'Class;procedure Obsluz_sygnaly isNastępny sygnał: Wskaźnik sygnału;Begin . . . Następny sygnał := . . . ; -- pobierz kolejny element . . . -- z kolejkii Zarejestruj (Nastepny_sygnał.all) ; -- powiązanie zgodne i typem . . . -- wskazanym w znaczni kuend Obsluz_sygnaly;

Nie obsłużone sygnały zostały zapamiętane w kolejce przechowującej różne typy sygnałów. Do zarejestrowania każdego sygnału z tej kolejki służy procedura obsłuż_sygnały. Zmienna Następny_sygnał tej procedury wskazuje kolejno na sygnały, które mają zostać zarejestrowane. Wywołanie procedury Zarejetruj ze zmienną Następny_sygnał jako aktualnym parametrem wywołania spowoduje, że dla każdego rodzaju sygnału zostanie wywołana właściwa implementacja procedury Zarejestruj (zgodna ze znacznikiem).

Page 83: Programowanie rozproszone

Asynchroniczna zmiana wątku sterowania

Czwarta forma instrukcji select, w skrócie ATC (ang. Asynchronoits Transfer of Control), powoduje asynchroniczną zmianę wątku sterowania. Jej składnia jest opisana następującym schematem:

select\wywołanie wejścia l instrukeja_delay\[instrukcja (...(] then abortinstrukcja (...) end select; -- część przerywana

Instrukcja składa się z dwóch części: przerywanej i, opcjonalnej, przerywającej Instrukcje części przerywającej są poprzedzone instrukcją wyzwalającą. Działanie instrukcji select polega na warunkowym wykonywaniu części przerywanej do momentu, gdy nastąpi jej przerwanie na skutek akcji wyznaczonej przez instrukcję wyzwalającą. Wtedy wykonuje się część przerywająca i kończy się działanie całej instrukcji. Jeżeli część przerywana zakończy się przed wystąpieniem akcji podanej w instrukcji wyzwalającej, wówczas oznacza to zakończenie całej instrukcji select (nie jest wykonywana cześć przerywająca"). Instrukcją wywołującą może być instrukcja delay [until], wywołanie wejścia zadania lub wywołanie wejścia obiektu chronionego. Akcją, powodującą przerwanie wykonywania części przerywanej jest odpowiednio: upływ czasu, podany jako parametr instrukcji delay [until] lub zakończenie instrukcji wywołania wejścia.

Przykład1:

select delay 1 . b ; -- instrukcja wyzwalająca Put_Line ("Obliczenia nie zakończyły się w wyznaczonym czasie"); then abort -- część przerywana funkcja_ rekursywna (X, Y) ;end select;

Page 84: Programowanie rozproszone

Przykład2: with Sygnały;use Sygnały; with Ada.Real_Time; use Ada.Real_Time; Nadzorca: Sygnał; protected Dane_dzielone isprocedure Pisz(D: in Dane); entry Czytaj (D: out Dane); private Wynik_obliczen: Dane; Wynik_dostepny: Boolean := False; end Dane_dzielone; task Konsument;

task Iterator; protected body Dane_dzielone is procedure Pisz(D: in Dane) is begin Wynik_obliczen := D;Wynik_dostepny := True; end Pisz; entry Czytaj(D: out Dane) when Wynik_dostepny is begin D := Wynik_obliczen; Wynik_obliczen := False; end Czytaj; end Dane_dzielone;

Page 85: Programowanie rozproszone

task body Konsument isbeginloop Dane_dzielone.Czytaj(Wynik) exit when Czas > Czekam; -- pobranie przybliżonych danychend loop; -- minął czas obliczeń Nadzorca . Nadaj ; -- żądanie przerwania obliczeń w Iteratorze Dane_dzielone . Czytaj (Wynik); -- pobranie ostatniego przybliżeniaend Konsument;

task body Iterator is begin Dane_dzielone.Pisz(Wynik); -- uzyskanie wyniku z minimalną -- żądaną dokładnością -- i zapisanie w obiekcie chronionymselect Nadzorca.Czekam; then abort -- obliczenie kolejnej iteracji przybliżonego wynikuloop . . . -- obliczenie Dane_dzielone . Pisz (Wynik) ; -- i zapisanie wyniku exit when Uzyskano_najlepszy_wynik;end loop;end select;end Iterator;

Page 86: Programowanie rozproszone

Awaryjne kończenie zadań

Przeciwieństwem normalnego zakończenia zadania jest zakończenie awaryjne, wymuszone instrukcją abort. Instrukcja ma następującą składnię:

abort nazwa_zadania {, nazwa_zadania};

Dowolne zadanie może awaryjnie zakończyć inne zadanie wykonując podaną instrukcję. Przykładem jest instrukcja abort:

abort Zad_l, Moja_Tab(5), R2.Zadanie, Wsk_Zadl.all;

Jeżeli zadanie jest kończone awaryjnie, to również kończone awaryjnie są wszystkie jego zadania potomne. Realizacja instrukcji abort zależy od stanu zadania, które ma być awaryjnie zakończone.Jeżeli zadanie jest zawieszone, np. realizowana jest instrukcja delay, zadanie czeka na aktywację lub na spotkanie, itp., to zostaje natychmiast zakończone. Jeżeli w momencie awaryjnego kończenia zadania nie jest ono zawieszone, to instrukcja abort powoduje, że ulegnie ono zakończeniu w najbliższym dogodnym momencie, zależnym od implementacji języka. Z takim zadaniem nie można się już więcej komunikować.

Page 87: Programowanie rozproszone

Przykłady programów w języku Ada ‘95

Przykład 1:

Przykład ilustruje zastosowanie typu zadaniowego z wyróżnikiem i typu wskaźnikowe go. Pojedyncze zadanie oblicza S!.Deklaracje typu zadaniowego Silnia i typu wskaźnikowego Wsk_silnia są zawarte w pakiecie Pakiet_silnia.

Ma on następującą specyfikację:

package Pakiet_silnia istask type Silnia (X: in Positive) is -- typ zadaniowy -- z wyróżnikiementry Wynik (Wy: out Long_Integer) ; -- wejście do pobierania -- wyniku end Silnia;type Wsk_silnia is access all Silnia; -- typ wskaźnikowyend Pakiet silnia;

Typ zadaniowy Silnia ma pojedyncze wejście Wynik (. . .) oraz wyróżnik x, typu Positive. Wejście Wynik (...) służy do pobierania wyniku obliczeń z zadania silnia. Przekazywanie argumentu do obliczeń jest realizowane w momencie kreacji i aktywacji zadania na podstawie typu wskaźnikowego Wsk_silnia i następuje przez podanie wartości wyróżnika z jakim zadanie ma być utworzone. Wynik obliczeń jest typu Long_integer, z zakresu [-2**31 .. +2**31-1].

Page 88: Programowanie rozproszone

Implementacja typu zadaniowego Silnia jest następująca:

package body Pakiet_silnia is task body Silnia is Odp: Long_Integer := 1; begin for i in 2 .. X loop Odp := Odp * Long_Integer(i); end loop; accept Wynik (Wy: out Long_Integer) do -- zwróć wynik Wy := Odp; end Wynik; end Silnia; end Pakiet_silnia;

Przykład 2:

Obiekty chronione można wykorzystać do synchronizacji i komunikacji zadań o różnych poziomach abstrakcji. Jednym z nich jest synchronizacja pary zadań przy użyciu sygnałów trwałych (ang. persistent signals}. Jedno z zadań czeka na możliwość kontynuacji tak długo, aż odbierze sygnał synchronizacji od drugiego zadania. Sygnał jest uważany za trwały, gdyż zadanie sygnalizujące może go nadać wcześniej niż zadanie partnerskie zgłosi gotowość do jego odbioru. Implementacja sygnałów trwałych jest oparta na obiekcie chronionym, który przechowuje informacje o oczekiwaniu na sygnał i nadaniu sygnału synchronizacyjnego. Ma ona następującą postać:

Page 89: Programowanie rozproszone

package Sygnały is protected type Sygnał is procedure Nadaj; entry Czekam;privateSygnal_nadszedl: Boolean ; end Sygnał;end Sygnały; package body Sygnały is protected body Sygnał is procedure Nadaj is begin Sygnal_nadszedl : = True; end Nadaj; entry Czekam when Sygnal_nadszedl is begin Sygnal_nadszedl := Fałse; end Czekaj; end Sygnał; end Sygnały;

Nadanie lub odebranie sygnału jest realizowane odpowiednio przez wywołanie procedury Nadaj lub wywołanie wejścia Czekam obiektu chronionego typu Sygnał.

Page 90: Programowanie rozproszone

Mechanizmy programowania systemów czasu rzeczywistego

System czasu rzeczywistego to system, w którym obliczenia są przeprowadzane równolegle z procesem zewnętrznym (otoczeniem) i mają na celu nadzorowanie, sterowanie lub terminowe reagowanie na zdarzenia zachodzące w tym procesie.Cechą charakterystyczną systemów czasu rzeczywistego jest ścisłe sprzężenie między procesem zewnętrznym (otoczeniem), w którym zachodzą zdarzenia, a systemem, który winien zdarzenia te rozpoznawać i reagować na nie w ograniczonym czasie (czas reakcji), określonym przez dynamikę tego otoczenia. Dynamika (szybkość zmian zachodzących w otoczeniu) może określać ostre (ang. hard] lub łagodne (ang. soft) wymagania względem zapewnienia odpowiedniego czasu reakcji. Wiąże się to zazwyczaj z dwoma klasami zastosowań systemów czasu rzeczywistego: systemy sterowania lub dowodzenia (wymagania ostre) oraz systemy komercyjne i biurowe, np. obsługa banków, systemy rezerwacji miejsc (wymagania łagodne).

Konstruowanie programu czasu rzeczywistego wymaga rozwiązania dwóch problemów.

Problem pierwszy to ułożenie odpowiedniego algorytmu reagowania na obsługiwane zdarzenia. Do zapisania takiego algorytmu wystarcza w zasadzie dowolny język programowania współbieżnego. Współbieżność jest tu istotna, gdyż wielozadaniowość jest naturalnym mechanizmem strukturalizacji programów czasu rzeczywistego — daje ona możliwość podziału programu na równolegle wykonywane zadania, które są odpowiedzialne za pojedyncze aktywności. Język musi mieć odpowiednie mechanizmy do komunikacji zadań i powinien mieć dodatkowe mechanizmy do reprezentowania zdarzeń zewnętrznych i reagowania na nie. Wymienione mechanizmy są dostarczane przez jądro języka (zadania, komunikacja synchroniczna i asynchroniczna) i konstrukcje opisane w aneksie C (obsługa przerwań, identyfikatory i atrybuty przerwań, itp.).

Page 91: Programowanie rozproszone

Drugi problem polega na ułożeniu algorytmu sterowania wykonywaniem programu, w konkretnym środowisku wykonawczym, który zapewni odpowiednią efektywność programu, czyli spełnianie przez zadania programu narzuconych ograniczeń czasowych. Do realizacji tego celu potrzebne są dodatkowe mechanizmy odpowiedniego szeregowania zadań i związane z nimi mechanizmy odmierzania czasu i reakcji na zdarzenia przy ograniczeniach czasowych.

Część mechanizmów związana z drugim problemem jest dostarczana przez mechanizmy jądra języka, ale większość jest opisana w aneksie D, o nazwie Systemy czasu rzeczywistego ( ang. Real-Time Systems, RTS}.

Podstawowe zagadnienia opisane w aneksie to:

- priorytety zadań i ich zmiany w wyniku komunikacji międzyzadaniowej lub interakcji zadań z obiektami chronionymi, - strategie szeregowania zadań, obsługa wejść zadań i obiektów chronionych, - uproszczenia modelu zadań ograniczające nie determinizm programów lub pozwalające na uproszczenia adowego środowiska wykonawczego, - dodatkowe mechanizmy synchronicznego i asynchronicznego sterowania wykonaniem zadań, - zegar monotoniczny o dużej rozdzielczości i dokładności.

Wyżej wymienione zagadnienia są opisane w kolejnych punktach niniejszego rozdziału.

Page 92: Programowanie rozproszone

Priorytety zadań

Przykładem metody szeregowania opartej na priorytetach dynamicznych jest metoda EDF (ang. Earliest Deadline First), gdzie priorytet zadania jest funkcją zbliżającego się momentu uruchomienia zadania i wykonania nadzorowanej przez zadanie akcji. Zadanie, któremu do momentu krytycznego pozostaje najkrótszy odcinek czasu, uzyskuje najwyższy priorytet. Priorytet zadań musi być zmieniany dynamicznie i mechanizmy dynamicznej zmiany priorytetu są dostępne dopiero w Adzie 95.

Priorytety bazowe

Zadaniom nadaje się priorytety statyczne przez użycie odpowiednich pragm, umieszczanych w specyfikacji zadań lub typów zadaniowych. Wartościami priorytetów są liczby całkowite o zakresie zdefiniowanym przez implementację.

subtype Any_Priority is Integer range -- zdefiniowany_przez__implementację; subtype Priority is Any_Priority range Any_Priority first .. -- zdefiniowany_przez__implementację; subtype Interrupt_Priority is Any_Priorityrange Priority'Last + l .. Any_Priority'Last; Default_Priority: constant Priority :=(Priority'First + Priority'Last)/2;

Page 93: Programowanie rozproszone

Przykład:

task Z is pragma Priority(8); end Z;

Typowi zadaniowemu T z nadaje się priorytet:

task type TZ(Task_Priority: System.Priority) is entry W (...); pragma Priority (Task_Priority); end TZ;

Jeżeli przy kreacji zadań o typie T Z nie używa się wyróżnika, to wszystkie utworzone zadania otrzymują ten sam priorytet.

Problem inwersji priorytetów

Podstawową własnością obiektów chronionych jest wzajemne wykluczanie dostępu do danych enkapsulowanych w obiekcie chronionym. Gdy z obiektami komunikują się zadania o różnych priorytetach, może wystąpić niepożądana inwersja priorytetów tych zadań. Inwersja priorytetów ma miejsce wtedy, gdy zadanie o niższym priorytecie wstrzymuje wykonywanie zadania o wyższym priorytecie.

Page 94: Programowanie rozproszone

Rozpatrzmy przykład programu z trzema zadaniami W, S, N, mającymi odpowiednio wysoki, średni i niski priorytet. Przyjmijmy, że zadania W i N mają dostęp do danych współdzielonych chronionych przez obiekt chroniony P. Zakładając, że program wykonuje się w systemie jednoprocesorowym, możliwa jest następująca sekwencja zdarzeń:

- Zadanie N zostaje zwolnione do wykonania i wykonując się wchodzi do P.- Zadanie S zostaje zwolnione do wykonania i wywłaszcza N (gdy N jest wewnątrz P).- Zadanie W zostaje zwolnione do wykonania i wywłaszcza S.- Zadanie W wywołuje wejście do P. Zadanie W nie może wejść do P, ponieważ zadanie N ma w danej chwili prawo wyłącznego dostępu do obiektu chronionego. Zatem w zostaje zawieszone i wykonuje się zadanie o kolejno najwyższym priorytecie, czyli S. W efekcie w musi czekać, aż zakończy się S.

Istnieje kilka metod dziedziczenia priorytetu. Aneks RTS, za pomocą pragmy:

pragma Locking_Policy(Ceiling_Locking)

udostępnia metodę ICPP (ang. Immediate Ceiling Pńority Protocol). Polega ona na tym, że każdemu obiektowi chronionemu P nadaje się pewien priorytet X, zwany granicznym, równy maksimum z priorytetów zadań komunikujących się z obiektem P. Jeżeli zadanie wchodzi do obiektu chronionego, to na czas interakcji z P dziedziczy ono priorytet X.

Page 95: Programowanie rozproszone

Szeregowanie wejść

Programista może definiować sposób obsługi wywołań wejść zadań i obiektów chronionych, a także sposób wyboru gałęzi w instrukcji select. Jak poprzednio, odpowiedni sposób określa się przez pragmę: pragma Queuing_Policy (Policy__Identifier) ;

Z podaną pragma są związane dwie standardowe metody: Fifo_Queuing oraz Prio rity_Queuing. Implementacja może definiować jeszcze inne metody. Domyślnie, w przypadku braku pragmy, stosuje się metodę Fifo_Queuing. Metoda umieszcza wszystkie wywołujące zadania w jednej kolejce i wybiera z niej do realizacji pierwsze zadanie, dla którego żądana usługa może być wykonana.

Metoda Priority_Queuing kolejkuje zadania według ich priorytetów. W obrębie instrukcji select i obiektów chronionych bierze się pod uwagę wejścia z otwartych gałęzi i wybiera do obsługi, spośród czekających zadań, zadanie o najwyższym priory tecie. Jeżeli na początku kolejek czekają zadania o jednakowym priorytecie, wówczas do obsługi jest wybierane wejście, które w programie jest tekstowo wcześniejsze. 

Page 96: Programowanie rozproszone

Priorytety dynamiczne

Często występują sytuacje, w których założenie stałości priorytetów nie jest właściwe.

Przykładem mogą być:

- potrzeba zmiany priorytetu zadania na skutek zmiany trybu pracy programu (z normalnego na awaryjny i skrócenie czasu reakcji na zachodzące zdarzenia), - szeregowanie zadań według strategii EDF.

W takich przypadkach niezbędne stają się zmiany priorytetów bazowych zadań. W celu przeprowadzania takich zmian zdefiniowano pakiet biblioteczny Ada . Dynamic_Prio-rities. Ma on następującą specyfikację:

with Ada.Task_Identification;with System;package Ada.Dynamic_Priorities isprocedure Set_Priority (Priority: System.Any_Priority;T: Ada.Task_Identification.Task_Id := Ada.Task_Identification.Current_Task); -- generuje wyjątek Tasking_Error, gdy wskazano zadanie nie istniejące -- lub zakończonefunction Get__Priority (T: Ada . Task_Identif ication . Task_Id : = Ada.Task_Identification.Current_Task) return System.Any_Priority; -- generuje wyjątek Tasking_Error, gdy wskazano zadanie nie istniejące -- lub zakończoneprivate . . . -- określone przez implementację end Ada.Dynamic_Priorities ;  

Page 97: Programowanie rozproszone

Mechanizmy programowania systemów rozproszonych

Program rozproszony składa się z pewnej liczby partycji, które mogą wykonywać się współbieżnie na jednej lub na wielu maszynach połączonych siecią komputerową.

Partycja jest zbiorem jednostek kompilacyjnych, połączonych w pewną całość w procesie konsolidacji. Program rozproszony składa się z co najmniej dwóch komunikujących się partycji.

Model systemu rozproszonego

Wyróżnia się partycje aktywne i pasywne. Partycje aktywne rezydują i wykonują się w węzłach przetwarzających systemu rozproszonego. Partycje pasywne rezydują w węzłach-składnicach systemu rozproszonego. Partycja aktywna może wywołać podprogram w innej partycji aktywnej. Takie wywołania są dopuszczalne tylko wtedy, gdy wywoływany podprogram jest deklarowany w jednostce bibliotecznej kategorii Remote_Call_lnterface. Każda wywołująca partycja, w swojej klauzuli kontekstowej with, musi zawierać nazwę jednostki bibliotecznej, w której umieszczono specyfikację wywoływanego podprogramu. Ilustruje to następujący szkielet programu.

package A is pragma Remote_Call procedure P (...) -- partycja aktywna udostępniająca Interface(A); end A; -- podprogramy do zdalnych wywołańwith A; -- partycja aktywna, w której występująpackage B is --  wywołania procedur partycji A A. P ; -- przykładowe wywołanie zdalneend B;

Page 98: Programowanie rozproszone

Wywołanie podprogramu jednej partycji aktywnej w drugiej partycji aktywnej jest nazywane zdalnym wywołaniem procedury (ang. Remote Procedurę Cali, RPC).

W przypadku wywołań RPC kompilator i konsolidator uzupełnia kod programu o tzw. korpusy wywołań (ang. stubs). Korpusy wywołań realizują zdalną komunikację w partycji wywołującej i wywoływanej.

Domyślnie, wywołania RPC są synchroniczne. Dodatkowo, pod pewnymi warunkami, wywołanie RPC może być realizowane asynchronicznie. Oznacza, to, że program w partycji wywołującej wznawia aktywność natychmiast po wywołaniu procedury zdalnej i nie czeka na realizację wywołania, jak to ma miejsce przy komunikacji synchronicznej. Implementacja może dostarczać dodatkowe mechanizmy komunikacji.

Przykład

Zbudowany w ramach projektu GNAT kompilator języka Ada 95 umożliwia budowanie programów rozproszonych. Podany niżej przykład programu rozproszonego zbudowano w oparciu o język konfigurowania partycji, podsystem komunikacji i narzędzia programowe tego właśnie systemu.

Page 99: Programowanie rozproszone

Rysunek 16.1. Powiązania komponentów systemu rozproszonego w Adzie

Aplikacje typu

„klient”

KorpusyAplikacjiklientów

System RPC

Podsieć komunikacyjna

System RPC

Aplikacje typu

„serwer”

Korpusyserwerów

Aplikacje zabudowane

Wygenerowaneprzez

kompilator

Dostarczone przez

implementację

Page 100: Programowanie rozproszone

Program rozproszony wyprowadza komunikat witaj świecie. Działa on według schematu klient-serwer. W przeciwieństwie do klasycznej wersji wyprowadzającej analogiczny napis, procedura Witaj pobiera tekst do wyprowadzenia ze zdalnego serwera, o nazwie Serwer_Komunikatow. Tekst programu rozproszonego jest zapisany w kilku plikach. Kompilacja, podział na partycje i alokacja partycji są wykonywane w wyniku polecenia gnatdist, które pobiera parametry z pliku konfiguracyjnego ww. cgf.

with Serwer_Komunikatow; -- plik: witaj. adbwith Text_IO; procedure Witaj is beginText_IO.Put_line (Serwer_Komunikatow.Komunikat_l); end Witaj; package Serwer_Komunikatow is -- plik: serwer_komunikatow . ads pragma Remote_Call_Interface; function Komunikat_l return String;end Serwer_Komunikatow;

package body Serwer_Komunikatow is -- plik: serwer_komunikatow. adb function Komunikat_l return String is begin return "Witaj świecie !"; end Komunikat 1;end Serwer_Komunikatow; configuration Witaj is Klient: Partition := (); procedure Witaj is in Klient; -- plik: ww. cfg Serwer: Partition := (Serwer_Komunikatow); -- zapisany z wykorzystaniem językaend Witaj;

Page 101: Programowanie rozproszone

Serwer jest implementowany w postaci pakietu. Procedura witaj wywołuje funkcje tego pakietu w ten sam sposób, jak to ma miejsce w programach nie rozproszonych. Jedyna różnica w budowie pakietu, to konieczność zastosowania pragmy Remote_ Call_lnterface, która wskazuje, że funkcje tego pakietu mogą być wywoływane zdalnie.Zakładając, że wszystkie podane pliki są w tej samej kartotece, polecenie: gnatdist wwpowoduje kompilację całego programu wraz z automatyczną generacją korpusów wywołań zdalnych, podziału na partycje, itd. Załadowanie i uruchomienie programu następuje po poleceniu Witaj.

Programowanie rozproszone

Programowanie rozproszone można inaczej nazwać przetwarzanie rozproszone. Wysyłanie komunikatu jest akcją wyższego poziomu, która może być łatwo zaimplementowana na fizycznie rozproszonych procesorach.

Page 102: Programowanie rozproszone

Algorytmy

- Pozwolenie

ALGORYTM CENTRALNEGO SERWERA

Stworzenie serwera koordynującego (udzielającego pozwoleń) wejście procesów do sekcji krytycznej jest jedną z podstawowych i najprostszych metod realizacji wzajemnego wykluczania w środowisku rozproszonym. Sposób działania takiego algorytmu przy założeniu, że istnieje tylko jedna sekcja krytyczna wydaje się mało skomplikowany i prosty do realizacji. Przy takich założeniach, proces, który chce wejść do zasobu dzielonego wysyła do serwera komunikat z zamówieniem i oczekuje na odpowiedź, często porównywaną do otrzymania żetonu dającego prawo do wejścia. W sytuacji gdy sekcja krytyczna jest pusta serwer natychmiast "wręcza" procesowi żeton, a proces może wejść do sekcji. Jeżeli żeton jest w posiadaniu innego procesu (żeton jest tylko jeden - spełnienie warunku, że w sekcji może w danej chwili znajdować się tylko jeden proces), wówczas proces zamawiający jest ustawiany w kolejce serwera. Wychodząc z sekcji krytycznej każdy z procesów zwraca żeton serwerowi, wysyłając jednocześnie komunikat o opuszczeniu sekcji. W przypadku gdy serwer udostępnia wejście do sekcji procesom znajdującym się w kolejce (w danej chwili tylko pierwszemu z nich), wówczas kieruje się priorytetem najstarszego wpisu. Pozwolenie na wejście uzyskuje proces, który ustawi się w kolejce najwcześniej. Poniższe rysunki obrazują kolejne kroki działania tego algorytmu dla czterech procesów. Na rysunku a) widać sytuację, w której w sekcji krytycznej znajduje się proces P3 (jest w posiadaniu żetonu), proces P4 wysłał już zamówienie żetonu do serwera wcześniej, natomiast proces P2 wysyła właśnie zamówienie na żeton. Kolejny krok algorytmu pokazuje zachowanie się poszczególnych procesów oraz żetonu. Proces P2 ustawił się w kolejce lokalnej serwera, żeton został zwrócony i natychmiast zostanie przekazany procesowi zamawiającemu najwcześniej, tzn.procesowi P4 (rys. b). Na rysunku c) proces P4 znajduje się w sekcji, na początku kolejki znalazł się proces P2, a kolejne procesy mogą wysyłać zamówienia.

Page 103: Programowanie rozproszone
Page 104: Programowanie rozproszone

Algorytm centralnego serwera.

Oczywiście algorytm ten spełnia warunki wymienione jako warunki wzajemnego wykluczania. Jednak system z zastosowaniem pojedynczego serwera jest podatny na awarie bardzo brzemienne w skutkach. Ze względu na to, że przez serwer przechodzą wszystkie operacje, jego uszkodzenie może być bardzo niebezpieczne dla całego systemu. Z tego powodu opisany algorytm jest jedynie obrazem przedstawiającym podstawy realizacji wzajemnego wykluczania i nie jest stosowany w rozwiązaniach systemów rozproszonych.

ALGORYTM LAMPORTA

Pierwszym algorytmem rozwiązującym problem wzajemnego wykluczania w środowisku rozproszonym jest algorytm Lamporta. Podobnie jak algorytm centralnego serwera spełnia warunki bezpieczeństwa, ruchu i uporządkowania. Algorytm zakłada, że wszystkie procesy w systemie posiadają lokalną kolejkę przechowującą komunikaty żądania innych procesów oraz znacznik czasu. Wysyłając komunikat, każdy proces zaopatruje go w numer dający informację, z którego procesu wysłano komunikat oraz w znacznik czasu w celu właściwego ich odbioru i interpretacji przy udzielaniu pozwolenia wejścia do sekcji. proces, który chce wejść do sekcji krytycznej, tworzy komunikat żądania, który oprócz tego, że jest wysyłany do innych procesów, jest także umieszczany w kolejce procesu wysyłającego żądanie. Komunikat taki ma swój znacznik czasu. Proces odbierający żądanie natychmiast wysyła odpowiedź ze znacznikiem czasu, a przybyłe żądanie umieszcza w swojej kolejce. Proces wysyłający żądanie będzie mógł wejść do sekcji krytycznej jeżeli jego komunikat będzie znajdował się na początku kolejki procesu wysyłającego oraz gdy jego komunikat zostanie odebrany przez inne procesy, a otrzymana odpowiedź będzie miała większy znacznik czasu od znacznika czasu żądania. Po opuszczeniu sekcji krytycznej proces usuwa własny numer z kolejki i zawiadamia wszystkie inne procesy w systemie o zwolnieniu zasobu dzielonego.

Page 105: Programowanie rozproszone

Wówczas procesy otrzymujące komunikat zwalniający "czyszczą" własną kolejkę z numerem procesu opuszczającego sekcję. Z powyższego opisu wynika, że najistotniejszymi założeniami algorytmu Lamporta są: utrzymanie lokalnej kolejki przez wszystkie procesy oraz komunikacja między wszystkimi procesami. Stosowanie tego rozwiązania jest jednak ograniczone ze względu na możliwość zbyt dużej liczby komunikatów w systemie (przy dużej liczbie procesów) i konieczność ich wymiany co powoduje powstawanie lokalnych kolejek o ogromnych rozmiarach. Algorytm Lamporta, przy założeniu, że w systemie jest N procesów, wymaga wymiany 3(N-1) komunikatów. Tak więc przy 3 procesach konieczna jest wymiana 6 komunikatów

Page 106: Programowanie rozproszone

Przykład działania algorytmu Lamporta.

Na powyższym rysunku przedstawiono przykładowy schemat działania algorytmu Lamporta w przypadku istnienia w systemie trzech procesów zainteresowanych wejściem do zasobu dzielonego. W sytuacji a) widać, że każdy z procesów wysyła zamówienie ze znacznikiem czasu do każdego z procesów w systemie, a swój znacznik ustawia w lokalnej kolejce. Następnie dokonywana jest klasyfikacja zamówień od wszystkich procesów i w lokalnej kolejce są ustawiane poszczególne zamówienia, a proces z najmniejszym znacznikiem uzyskuje dostęp do sekcji krytycznej po otrzymaniu odpowiedzi od innych procesów. Odpowiedzi procesów są wysyłane natychmiast po otrzymaniu komunikatów zamówień i po stwierdzeniu przez proces P3, że mają one znaczniki czasu późniejsze oraz to, że jego identyfikator jest umieszczony na początku lokalnej kolejki wchodzi on do sekcji krytycznej. W momencie opuszczania przez proces zasobu dzielonego wysyła komunikaty zwalniające do pozostałych procesów w celu umożliwienia im wejścia do sekcji. W powyższym przykładzie widać, że kolejnym procesem, który wejdzie do sekcji będzie proces, który znajduje się najwyżej w kolejkach lokalnych tj. proces P1. Zasada postępowania jest dla tego procesu jest identyczna.

Page 107: Programowanie rozproszone

ALGORYTM RICARTA I AGRAWALI

Kolejnym rozwiązaniem realizacji wzajemnego wykluczania jest opracowany przez Ricarta i Agrawalę algorytm oparty na rozproszonym uzgadnianiu zwany często jako algorytm z zastosowaniem zegarów logicznych. Podobnie jak w algorytmie Lamporta, również w tym rozwiązaniu każdy proces chcący wejść do sekcji krytycznej rozsyła komunikat do wszystkich procesów w systemie i może wejść do sekcji dopiero wówczas, gdy dostanie odpowiedź od pozostałych komunikatów. Założenia tego algorytmu są następujące:

- istnieje jedna sekcja krytyczna (dla ułatwienia), - wszystkie procesy znają wzajemnie swoje adresy, - wszystkie komunikaty zostaną w końcu dostarczone, - każdy proces utrzymuje zegar logiczny (znacznik czasu)

Można zauważyć, że algorytm ten jest modyfikacją rozwiązania zaproponowanego przez Lamporta. Procesy wysyłają komunikaty, które posiadają znacznik czasu T i identyfikator nadawcy p (T, p). Proces, który otrzymał od innego procesu komunikat zamawiający wejście do sekcji działa następująco:

1) jeżeli sam wcześniej nie wysłał prośby (nie czeka na dostęp), to odpowiada pozytywnie 2) jeżeli sam wcześniej wysłał prośbę i jeszcze nie uzyskał wszystkich odpowiedzi, to o tym kto pierwszy wykona sekcje krytyczna decyduje czas wysłania próśb:

Page 108: Programowanie rozproszone

a. jeżeli zapytywany proces wysłał swoja prośbę później, to także odpowiada pozytywnie b. jeżeli prośby zostały wysłane w tym samym momencie, to o kolejności decydują priorytety (np. jeżeli zapytany proces ma wyższy numer to, odpowiada pozytywnie) c. w każdym innym przypadku zapytywany proces wstrzymuje się z odpowiedzią, aż do chwili, gdy sam skończy wykonywać swoja sekcje krytyczna - wówczas odpowiada pozytywnie wszystkim, którym jeszcze nie odpowiedział.

Wejście do sekcji krytycznej może nastąpić tylko wówczas gdy proces wysyłający żądanie otrzyma od każdego innego procesu odpowiedź. Gdy proces wykonał swoje zadania w sekcji krytycznej może ją opuścić informując jednocześnie o swym kroku procesy których żądania przetrzymuje w lokalnej kolejce. W porównaniu z algorytmem Lamporta wprowadzono tutaj kolejkę logiczną opierającą się o wartości 0 i 1 oraz zrezygnowano z komunikatu zwalniającego. Dzięki zastosowaniu tych zmian zmalała liczba komunikatów niezbędnych do wejścia do sekcji krytycznej z 3(N-1) w algorytmie Lamporta do 2(N-1).

Page 109: Programowanie rozproszone

Przykład działania algorytmu Ricarta i Agrawali.

Powyższa ilustracja przedstawia sytuację, w której mamy do czynienia z trzema procesami, z których współbieżne zamówienie wejścia do sekcji wysyłają procesy P1 i P2. Oba zamawiające procesy mają znaczniki czasu odpowiednio P1- 41, a P2 - 34. Proces P3 w przypadku otrzymania zamówienia odpowie natychmiast (nie jest zainteresowany wejściem do zasobu dzielonego w danej chwili). Inaczej sytuacja przedstawia się w przypadku procesów zainteresowanych wejściem do sekcji. Proces P2 odbierając komunikat zamówienie od procesu P1 stwierdzi, że jego własne zamówienie ma znacznik czasu mniejszy niż zamówienie procesu P1. Z tego powodu nie udzieli odpowiedzi. Natomiast proces P1 otrzymując komunikat od procesu P2 z mniejszym znacznikiem czasu odpowie natychmiast umożliwiając wejście do sekcji procesowi P2. W momencie opuszczenia sekcji krytycznej przez proces P2 wysyła on odpowiedź do procesu P1 umożliwiając mu wejście do sekcji. Wprawdzie jest to algorytm rozproszony, jednak awaria dowolnego procesu uniemożliwi działanie algorytmu. Kolejną wadą jest to, że wszystkie procesy przetwarzają wszystkie zamówienia co osłabia wydajność systemu.

Page 110: Programowanie rozproszone

ALGORYTM MAEKAWY

Ciekawe podejście do problemu wzajemnego wykluczania przedstawił w swoim rozwiązaniu Maekawa. Mając do dyspozycji N procesów w systemie Maekawa podzielił go na podzbiory z uwzględnieniem pewnych warunków. Pierwszy warunek zwany "regułą równego wysiłku", mówi, że podział systemu na podzbiory odbywa się w ten sposób, że każdy podzbiór ma taki sam rozmiar. "Reguła niepustego przejęcia" oznacza, że dla dowolnej pary podzbiorów istnieje element, który należy do każdego z nich. "Reguła równej odpowiedzialności" oznacza, że każdy proces zawiera się dokładnie w takiej samej liczbie podzbiorów. Ostatni warunek oznacza, że każdy proces zawiera się we własnym podzbiorze i zwany jest "zawieraniem się we własnym podzbiorze". Sposób działania algorytmu jest nieco bardziej skomplikowany ze względu na dość dużą liczbę komunikatów. Proces, który zamierza wejść do sekcji krytycznej wysyła komunikat żądanie do wszystkich procesów z własnego podzbioru. Komunikat ten jest zaopatrzony w numer kolejności (lub znacznik czasu) większy niż numery kolejności odebrane wcześniej przez ten proces lub wcześniej zauważone przez proces w systemie. Każdy z procesów w podzbiorze otrzymujących komunikat żądanie wysyła następnie do procesu chcącego wejść do sekcji krytycznej komunikat blokada, a wcześniej zaznacza siebie jako zablokowany, przy uwzględnieniu warunku, że wcześniej nie został zablokowany. Jeżeli dojdzie do sytuacji, że proces był już wcześniej zablokowany wówczas komunikat żądanie jest umieszczany w lokalnej kolejce, a do procesu chcącego korzystać z zasobu wysyłany jest komunikat niepowodzenie. W przypadku gdy komunikat żądania ma niższy numer niż komunikat już blokujący proces w podzbiorze wówczas wysyłany jest komunikat pytanie.

Page 111: Programowanie rozproszone

Proces otrzymujący komunikat pytanie kasuje komunikat blokowanie (jeżeli w kolejce jest komunikat niepowodzenie) i zwraca komunikat opuszczenie. Po odebraniu komunikatu pytanie i opuszczeniu sekcji krytycznej proces wysyła odpowiedź. Proces, który otrzymał komunikat opuszczenie blokuje się dla komunikatu żądania z najmniejszym numerem kolejności. Jeżeli zaistnieje sytuacja w której wszystkie procesy z podzbioru wysłały komunikat blokowanie to proces wysyłający żądanie może wejść do sekcji krytycznej. Opuszczając sekcję proces wysyła do wszystkich procesów danego podzbioru komunikat odpowiedź. proces otrzymujący komunikat odpowiedź usuwa żądanie blokujące proces z lokalnej kolejki i znowu przechodzi w stan blokowania dla procesu, który wysłał komunikat żądanie z najmniejszym numerem kolejności. Poniższa ilustracja przedstawia przykładowy sposób działania tego algorytmu, przy założeniu, że w systemie występuje sześć procesów:

Page 112: Programowanie rozproszone

Zasada działania algorytmu Maekawy.

System składa się z sześciu procesów, z których proces P1 chce wejść do sekcji krytycznej. Wysyła zatem komunikaty żądania ze znacznikiem do procesów własnego podzbioru tj. P2 i P3 oraz do samego siebie. Procesy otrzymujące komunikat żądanie wysyłają komunikaty blokowanie do procesu P1, który może wejść do sekcji krytycznej. W czasie gdy P1 przebywa i korzysta z zasobu dzielonego do sekcji krytycznej chce wejść proces z innego podzbioru tj. P6 i wysyła żądania do wszystkich procesów ze swojego podzbioru w tym także do procesu P2 (oraz do P4). Proces P4 natychmiast zwraca komunikat blokowanie natomiast P2 musi ustawić żądanie w kolejce, gdyż jest już zablokowany przez proces P1 (aktualnie korzystający z zasobu). Proces P2 jest w tym momencie swoistym arbitrem. W momencie opuszczenia sekcji przez proces P1 otrzyma on odpowiedź i wówczas będzie mógł wysłać komunikat blokowanie do procesu P6, który w ten sposób będzie mógł wejść do sekcji.

Page 113: Programowanie rozproszone

ALGORYTM LE LANNA

- żeton

Jednym z prostszych algorytmów opartych na zasadzie żetonu jest algorytm podany przez Le Lanna.Realizacja wzajemnego wykluczania pomiędzy procesami może odbywać się w oparciu o pierścień logiczny zbudowany z procesów znajdujących się w systemie. Budowa takiego pierścienia jest bardzo prosta. Każdy proces ma swój adres, a komunikaty są przekazywane w jednym ustalonym kierunku, wokół całego pierścienia. Żeton jest uzyskiwany razem z komunikatem i zachowuje się jak kulka ruletki z tą różnicą, że zatrzymuje się ("odwiedza") przy każdym procesie w pierścieniu. Każdy proces w systemie zna adres swojego sąsiada w pierścieniu. Sposób wejścia do sekcji jest mało skomplikowany i polega na otrzymaniu przez zainteresowany wejściem proces żetonu wraz z komunikatem. Na początku działania algorytmu należy przyjąć, który proces będzie posiadał żeton. Jako kryterium można tu przyjąć numer procesu (najmniejszy lub największy). Proces otrzymując żeton od sąsiada wchodzi do sekcji (jeżeli tego żądał), a wychodząc z sekcji przekazuje go do sąsiada zgodnie z kierunkiem poruszania się komunikatów w pierścieniu. Jeżeli proces nie był zainteresowany korzystaniem z zasobu dzielonego, przekazuje żeton natychmiast po jego otrzymaniu. W sytuacji w której żaden z procesów nie jest zainteresowany korzystaniem z zasobu żeton krąży bez przerwy w pierścieniu.

Page 114: Programowanie rozproszone

Przyjęcie zasady poruszania się żetonu w jednym kierunku daje gwarancje bezpieczeństwa i żywotności. W rozwiązaniu tym nie ma jednak pełnienia zasady uprzedniości, tzn. zasady mówiącej, że pozwolenie na wejście do sekcji otrzymuje proces zgłaszający żądanie najwcześniej. Sytuacja w której żaden z procesów nie wchodzi do sekcji krytycznej jest niekorzystna ze względu na to, że bez przerwy krążący żeton zajmuje jeden z kanałów komunikacyjnych. Również awaria jednego z procesów w systemie może spowodować awarię całego systemu. Naprawa takiej usterki polega wówczas na usunięciu uszkodzonego procesu z systemu. Może zaistnieć sytuacja, że uszkodzeniu ulegnie proces, który w danej chwili przechowywał żeton. Wówczas po upewnieniu się, że proces rzeczywiście uległ awarii zwołuje się elekcję, która wybiera proces do regeneracji żetonu.

Page 115: Programowanie rozproszone

ALGORYTM SUZUKI I KASAMI

Algorytm Suzuki i Kasami jest rozwiązaniem eliminującym wadę algorytmu zaproponowanego przez Le Lanna. Jego główna idea polega na tym, że przekazuje żeton oraz wykorzystuje komunikaty żądania przesyłane do procesów w systemie, w ten sposób, że żeton może być przekazywany bezpośrednio do procesu wysyłającego komunikaty. Tak więc wykluczone jest tu przekazywanie żetonu gdy w systemie nie ma procesu, który w danej chwili chce wejść do sekcji krytycznej. Proces chcący korzystać z zasobu dzielonego przy założeniu, że nie posiada w danej chwili żetonu wysyła do pozostałych procesów w systemie komunikat żądanie. Proces będący w tym czasie w posiadaniu żetonu natychmiast przesyła go do procesu wysyłającego komunikat żądanie. Wyjątkiem jest sytuacja, w której proces z żetonem wykonuje właśnie sekcję krytyczną (tzn. korzysta w danej chwili z zasobu dzielonego). Wówczas przekazanie żetonu nastąpi po zakończeniu wykonywania operacji na zasobie dzielonym przez proces obecnie korzystający z zasobu. Jeżeli żaden proces nie żąda wejścia do sekcji to żeton "stoi w miejscu" i proces posiadający żeton może wykonywać sekcję wielokrotnie. Aby algorytm ten mógł działać poprawnie procesy i żeton zawierają dodatkowe informacje. Żeton zawiera zatem wektor, w którym na kolejnych pozycjach znajdują się numery obsłużonych żądań (na pierwszej pozycji znajduje się numer kolejności ostatnio obsłużonego żądania). Oprócz tego żeton zawiera również kolejkę żądań procesów wraz z ich identyfikatorami. Również procesy przechowują w swojej pamięci lokalnej wektor żądań z największym numerem kolejności otrzymanym do chwili wysłania w komunikacie żądania oraz własny numer kolejności. Jeżeli proces chce wejść do sekcji i nie jest w posiadaniu żetonu zwiększa jednostkowo własny numer kolejności oraz numer kolejności w wektorze żądań. Komunikat żądania wysyłany jest już z nową wartością własnego numeru. Procesy odbierające komunikat żądania porównują wartości własne procesu wysyłającego z wartością w wektorze żądań. Jeżeli wyniki porównań dadzą informacje, że komunikat nie jest "przeterminowany" oraz, że żądanie nie zostało wcześniej obsłużone to żeton zostanie przekazany. Proces opuszczający sekcję i wysyłający żeton zapisuje w wektorze żetonu, że żądanie zostało obsłużone

Page 116: Programowanie rozproszone

Schemat przedstawiający przekazanie żetonu w algorytmie Suzuki i Kasami.

Na rysunku a) proces P3 jest w posiadaniu żetonu. Następnie chęć korzystania z sekcji krytycznej wyraża proces P4 i wysyła komunikaty żądania do wszystkich procesów w systemie z zachowaniem zasady zwiększenia wartości przechowywanych w pamięci. procesy w systemie odbierając taki komunikat modyfikują odpowiednio własne wektory żądań. Następnie porównywane są wartości poszczególnych wektorów i proces zostaje przesłany do procesu, który wysłał komunikat żądanie (po uprzednim upewnieniu się, że żądanie to nie zostało jeszcze obsłużone).

Page 117: Programowanie rozproszone

ALGORYTM NAIMI, TREHEL I ARNOLDA

Podobnie jak w rozwiązaniu z zastosowaniem algorytmu centralnego serwera również w algorytmie Naimi, Trehel i Arnolda proces chcący wejść do sekcji krytycznej wysyła swoje żądanie do jednego punktu, a nie do wszystkich procesów w systemie. W algorytmie centralnego serwera żądanie było wysyłane do centralnego punktu - serwera. Natomiast w algorytmie zaproponowanym przez Naimi, Trehel i Arnolda, żądanie jest przesyłane do procesu, który się zmienia tzn. nie jest to przez cały czas ten sam proces. Po wysłaniu komunikatu tylko do jednego procesu, proces wysyłający żądanie oczekuje na żeton. W algorytmie tym wszystkie procesy są dodatkowo wyposażone w zmienne zawierające informacje pomocnicze, niezbędne do właściwej realizacji i pracy tego algorytmu. Zmienne te zawierają identyfikator następnego w kolejności procesu zgłaszającego chęć wejścia do sekcji (zmienna "następny"), identyfikator procesu, który jest w danej chwili punktem centralnym w systemie i do którego są przesyłane komunikaty żądania (zmienna "ojciec"), logiczny identyfikator dający informację, czy proces posiada w danej chwili żeton czy nie (zmienna "stan obecny") oraz drugi logiczny identyfikator, który "trzyma' wartość 1 (true) w przypadku wysłania komunikatu do momentu zaprzestania korzystania z zasobu (zmienna "żądanie"). Przekazanie żetonu z procesu posiadającego żeton do innego, może nastąpić tylko gdy sam proces (posiadacz żetonu) opuścił sekcję i kolejka nie jest pusta oraz gdy żądanie nadejdzie w chwili gdy nie wykorzystuje żetonu. Proces zamierzający wejść do sekcji krytycznej ustawia wartość zmiennej żądanie na true (wartość 1). Jeżeli równocześnie zmienna ojciec będzie różna od Nil to żądanie zostanie wysłane do procesu wskazywanego przez zmienną ojciec. Wówczas następuje przekierowanie zmiennej ojciec na wartość Nil. Proces, który otrzyma żądanie sprawdza swój stan.

Page 118: Programowanie rozproszone

Jeżeli jest w stanie oczekiwania na żeton musi sprawdzić wartość zmiennych żądanie i w przypadku gdy wynosi on true to zmienna następny wskazuje na proces inicjujący żądanie. Jeżeli jest ona równa false to proces odbierający żądanie jest w posiadaniu żetonu. Przed wykonaniem tych czynności proces otrzymujący żądanie sprawdza zmienną ojciec. Następnie żeton jest przekazywany do procesu, który wysłał żądanie. Po otrzymaniu żetonu zmienna stan jest równa true, a przy wyjściu z sekcji zmienna żądanie jest równa false.

Page 119: Programowanie rozproszone

Schemat działania algorytmu Naimi, Trehel i ArnoldaW sytuacji przedstawionej na powyższym rysunku proces P1 jest posiadaczem żetonu i znajduje się w sekcji krytycznej. Linia ciągła w tym schemacie reprezentuje zmienną ojciec i wskazuje proces do którego ma być przekazywany komunikat żądanie. Linia przerywana natomiast reprezentuje zmienną następny. Proces P2 zamierzający wejść do sekcji krytycznej wysyła żądanie do aktualnie wskazywanego procesu P1. W momencie opuszczenia sekcji przez proces P1 żeton zostanie przekazany do procesu P2. Jeżeli jako następny będzie chciał wejść proces P4, wysyła żądanie do procesu P1, a natychmiast po tym zdarzeniu proces P1 wyśle żądanie do P2. Wówczas zmienna następny w procesie P2 będzie wskazywała na proces P4, a zmienne ojciec w procesach P1 i P2 na proces P4. W momencie opuszczenia sekcji przez proces P2, żeton zostanie przekazany do procesu P4.