SOP121/ćw - temat G

Problemy współbieżności; język BACI.

Tam gdzie mamy współbieżnie działające procesy które ze sobą współpracują lub współzawodniczą o zasoby tam pojawiaja się problemy współbieżności ...

Mały przykład problemu współbieżności :
mamy zmienną "licznik" dostępną dla dwóch procesów P0 i P1;
początkowa wartość licznika wynosi 0;
procesy P0 i P1 100-krotnie zwiększa licznik o 1 przy pomocy następującego kodu:

w danej chwili wartość licznika powinna być dokładnie równa liczbie wszystkich "zwiększeń o 1", a po zakończeniu działania obu procesów powinno być: licznik = 200. Okazuje się że jeśli oba procesy równocześnie zwiększają licznik to wynik końcowy może nie być zadowalający - niebezpieczeństwo pojawia się wtedy gdy procesor przełączy się na drugi proces między liniami l2 i l3. Aby uniknąć tego niebezpieczeństwa trzeba zapewnić aby w danej chwili tylko jeden proces zwiększał licznik. Innymi słowy trzeba potraktować operację zwiększania jako sekcje krytyczną i zapewnić wzajemne wykluczanie.

Definicje niektórych pojęć związanych ze współbieżnością :

 

Język programowania współbieżnego BACI.

Aby oswoić się z narzędziami używanymi do rozwiązywania "problemów współbieżności" będziemy rozwiązywać te problemy przy pomocy języka BACI = (Ben-Ari Concurrent Interpreter). Język ten występuje w 2 wersjach Pascalo-podobnej i C-podobnej ...
 
Oprogramowanie BACI (DOS):  
  • Kompilator BACI; Pascalo-podobny (bapas.exe)
  • Kompilator BACI; C-podobny (bacc.exe)
  • Interpreter BACI (bainterp.exe)
  • DisAssembler BACI (badis.exe)
  • Dokumentacja; Pascalo-podobny (guidepas.pdf)
  • Dokumentacja; C-podobny (baci.ps)
  • Dokumentacja; C-podobny (baci.pdf)
  • Krótki opis (baci.htm)
  • Wszystkie przykłady (pliki *.pm, *.cm i *.inc) jak i oprogramowanie BACI należy skopiować do własnego katalogu i tam wykonywać odpowiednie eksperymenty !

    UWAGI od interpretera DOS-owego:
    W sciezkach do katalogu nie powinno byc żadnych znaków zakazanych przez DOS.

     

    Sposób kompilowania i uruchamiania:

    Uruchomienie kompilatora dla pliku zródłowego "np00.pm":

    Uruchomienie interpretera: Kazdy program powinien wyswietlac jakies napisy, można go wtedy zatrzymać przy pomocy Ctrl-C.

    Język BACI może służyc do eksperymentów z semaforami i monitorami, ale także do tworzenia nowych "narzędzi" i do eksperymentalnego ich sprawdzania, a to dzięki takim elementom jak: procedury niepodzielne ze słowem kluczowym atomic (które nie mogą być wywłaszczone), funkcje specjalne takie jak: suspend(), revive(), which_proc(). W szczególności można tworzyć własne semafory o zmodyfikowanej definicji.

    Język BACI jest bardzo realistycznym symulatorem współbieżnych procesów. Jeśli zachodzi potrzeba przełączenia procesora, to nowy proces jest wybierany drogą losowania, tak więc nie można nic zakładać co do "względnej prędkości" działania procesów. Mówiąc dokładniej, jeśli mamy procesy ProcA i ProcB wykonujące rozkazy A1,A2,A3,  B1,B2,B3 (odpowiednio) to rzeczywista kolejność wykonania tych rozkazów jest dowolna, np: A1,A2,B1,A3,B2,B3 lub A1,B1,B2,A2,A3,B3 (są to tzw "przeploty"; wszystkie przeploty są możliwe).

    W języku BACI posługujemy się pojęciem "proces" chociaż bardziej odpowiednie byłoby pojęcie "wątek", gdyż tym słowem posługuje się instrukcja obsługi.

    Język BACI (bapas.exe) jest bardzo podobny do uproszczonego języka Pascal.
    patrz: dokumentacja !!!
     

    Uruchamianie wspólbieżnych procesów:

    program np00;

    atomic procedure pisz(nr, i:integer);
    begin
    writeln( nr, ":", i); i:=i+1;
    end;

    procedure proc(nr:integer);
    var i:integer;
    begin
    i:=0;
    while true do
    begin
    pisz(nr, i); i:=i+1;
    end;
    end;

    begin
    cobegin
    proc(1);
    proc(2);
    proc(3);
    coend;
    end.

    Powyższy kod powoduje uruchomienie 3 współbieżnie działających procesów z których każdy wykonuje pętle wyświetlającą napis nr_procesu:nr_iteracji.

    Ogólne zasady działania programów w języku BACI: 
  • Każdy proces wykonuje kod pewnej procedury
  • (jednak kilka procesów może wykonywać kod tej samej procedury). 
  • Każdy proces posiada własny stos, który jest używany do implementacji zmiennych lokalnych procedur
  • (dlatego każdy proces w powyższym przykładzie ma własną kopię zmiennej "i"). 
  • Wszystkie procesy mają dostęp do zmiennych globalnych
  • (w powyższym przykładzie nie jest to wykorzystywane).

    UWAGA: Słowo atomic służy głównie do definiowania (niepodzielnych) "rozkazów procesora" o ile zakładamy że procesor jest wyposażony w takie rozkazy; NIE należy go używać do rozwiązywania problemów chyba że wyraźnie się na to zezwala w treści zadania !.
    Rozwiązując zadanie wolno używać TYLKO narzędzi które w danym momencie są omawiane !!!.
     

    Zadanie 90


    Skompiluj i uruchom przykład "np00.pm". Spróbuj wziąć w komentarz słowo "atomic" i zobacz co się zmieni w działaniu programu ...

     

    Semafory.

    Aby sprawdzić czy dane narzędzie jest skuteczne zazwyczaj rozwiązuje się przy jego pomocy następujące "klasyczne" problemy współbieżności:
  • Problem producenta i konsumenta. Producent produkuje elementy, a konsument je konsumuje jednak czynią to z różnymi prędkościami. Używa się bufora do przechowywania elementów. Gdy bufor jest pusty konsument musi czekać, gdy bufor jest pełny producent musi czekać. (To jest wersja problemu "ze skończonym buforem".)
  • Problem czytelników i pisarzy. Istnieje czytelnia i 2 grupy procesów: czytelnicy i pisarze. Chcą oni wchodzić do czytelni. W danej chwili w czytelni może przebywać dowolna ilość czytelników lub tylko jeden pisarz. Ten problem to "abstrakcja" dostępu do bazy danych.
  • Problem pięciu filozofów. Pięciu głodnych filozofów siedzi przy okrągłym stole, przy talerzach z rybą. Aby jeść rybę potrzebne są 2 widelce, jednak między dwoma sąsiednimi filozofami leży tylko 1 widelec. Wszyscy filozofowie na przemian myślą i jedzą (w pętli). Zauważ że wprowadzenie wzajemnego wykluczania dla każdego widelca nie rozwiązuje problemu: może się zdarzyć że wszyscy filozofowie zaczną podnosić widelce począwszy od lewego - wtedy nie beda mogli podnieść prawego widelca - wystąpi tzw blokada ... Druga uwaga: w problemie tym robi się zazwyczaj zalożenie że filozofowie NIE są ponumerowani "po kolei" w kolejności krzeseł przy stole, gdyż w takim wypadku rozwiązanie jest łatwe (można wykorzystać ten numer).
  • Co należy rozumieć przez rozwiązanie problemu ?
    Rozważmy to na przykładzie "problemu pięciu filozofów".
    Mamy 5 procesów wykonujących funkcje Filozof():

    Do procedury "Filozof" należy dodac fragmenty kodu zapewniające że filozofowie będą się zachowywać prawidłowo, zgodnie z definicją problemu pięciu filozofów. Np dwaj siedzący obok siebie filozofowie nie mogą równocześnie jeść (to jest tzw własność bezpieczeństwa). Z drugiej strony każdy filozof powinien się kiedyś najeść (to jest tzw własność żywotności). Bardzo przydatny jest proces kontrolny, działający niezależnie od innych, który kontroluje bezpieczeństwo i żywotność (w tym wypadku po prostu pokazuje co robią filozofowie ...).
     
    .................................................................. 

    Jednym z narzędzi do rozwiązywania problemów współbieżności (wynalezionym przez Dijkstrę w 1965) jest semafor.

    Definicja semafora ogólnego (wg Ben-Ariego):
    Semafor to zmienna całkowita "S", na której można wykonywać następujące (niepodzielne) operacje:

  • Opuszczenie, P(S), wait(S): Jeśli S>0 to S=S-1, w przeciwnym wypadku wstrzymaj działanie procesu wykonującego tę operację.
  • Podniesienie, V(S), signal(S): Jeśli są procesy wstrzymane w wyniku opuszczania semafora S, to wznów jeden z nich, w przeciwnym razie S=S+1.
  • Można nadać semaforowi wartość początkową.
  • Przykład: sekcja krytyczna chroniona przy pomocy semaforów ...

     
    A oto kolejny prosty przykład (np02.pm).
    Są tam dwie zmienne konto1 i konto2 między którymi dokonuje się "przelewów". Przelewów dokonuje wiele procesów naraz. Suma kont musi pozostać taka sama po dowolnym czasie. Jest jasne, że operacja dokonywania przelewu jest sekcją krytyczną, związaną z zasobem jakim są obie zmienne. Zadaniem procesu pokazKonta jest pokazywanie stanu zmiennych. Wzajemne wykluczanie jest realizowane przy pomocy semaforów.

    Zadanie 91


    Przeanalizuj przykład "np02.pm". Poeksperymentuj trochę z tym programem. Spróbuj zmienić początkową wartość semafora na 0 oraz na 2. Spróbuj także wziąć w komentarz wywołania wait() i signal().
     
    Zadanie 91a

    Zaprojektuj eksperyment w języku BACI pokazujący niebezpieczeństwo o którym mowa tutaj. Powinny istnieć zmienne globalne "licznik", "i1", "i2" oraz 2 procesy "proc1" i "proc2". Proc_k (k=1,2) zwiększa o 1 zmienna "licznik" oraz "i_k". Istnieje także proces kontrolny procKontr pokazujący dwie wartości: i1+i2 oraz licznik. Oczywiści gdyby wszystko działało prawidłowo to te dwie liczby powinny być równe !. Kod zwiększający zmienne traktujemy jako sekcje krytyczną. Wykonaj eksperyment z ochroną sekcji krytycznej (przy pomocy semafora) i bez ...
    Do sprawozdania wstaw kody programów w języku BACI oraz końcówki produkowanych przez nie wydruków.

    Zadanie 91b


    Zbadaj skąd się bierze błędne działanie przykładu "np02.pm" (niewłaściwa suma konto1+konto2) jeśli usuniemy operacje semaforowe. Czy problem powstaje gdy procesor przełącza się na inny proces między modyfikacją zmiennej konto1 a modyfikacją zmiennej konto2 czy też są jakieś inne przyczyny błędu?

     

    Definicja semafora binarnego:
    Semafor binarny to zmienna "S", przyjmująca wartości 0 i 1, na której można wykonywać następujące (niepodzielne) operacje:

  • Opuszczenie, PB(S), wait(S): Jeśli S==1 to S=0, w przeciwnym wypadku wstrzymaj działanie procesu wykonującego tę operację.
  • Podniesienie, VB(S), signal(S): Jeśli są procesy wstrzymane w wyniku opuszczania semafora S, to wznów jeden z nich, w przeciwnym razie S=1.
  • Można nadać semaforowi wartość początkową, 0 lub 1.
  • Uwaga !!!: w języku BACI jeśli S=1 to signal(S) powoduje błąd! (jest to odstępstwo od tradycyjnej definicji semafora binarnego mające na celu ułatwienie debugowania)

    Zasada programowania z użyciem semaforów: 
    1. Interpretacja wartości semafora powinna być tak dobrana, aby operacje wait() i signal() były dla nas przydatne i wygodne. 
    2. Interpretacja ta powinna być zapisana w komentarzu obok deklaracji semafora. 
     

    Poniżej znajdują się zadania dotyczące semaforów.
    Nieco trudniejsze zadania będą oznaczone przez (*) i (**).
     

    Zadanie 92


    Oto niepoprawne rozwiązanie problemu "producenta/konsumenta", dla nieskończonego bufora, przy użyciu semaforów binarnych oraz zmiennej pomocniczej "N" (np03.cm). W przykładzie tym produkowane elementy są wybierane losowo, konsument wyświetla co skonsumowal a producent co wyprodukował ...
    Należy przeanalizować i zrozumieć na czym to rozwiązanie polega. Potem proszę znaleźć błąd i naprawić go. Aby ułatwić sobie analizę działania programu, uruchamiaj interpreter z przekierowaniem do pliku (bainterp np03 > qqq.txt), oczywiście jeśli jego działanie jest "skończone" np z powodu błędu. Dokładnie przeanalizuj działanie programu tuż przed wystąpieniem błędu. Zobacz co się dzieje z semaforem "Zwłoka". Linie "atrapa=321" mają na celu zwiększenie prawdopodobieństwa wystąpienia błędu.
    Uwaga: kod trzeba przetłumaczyć na język BACI/Pascal !.
    Wskazówka: coś niedobrego zaczyna się dziać gdy:  
    void Producent()
    {
      int atrapa;

      while(1) {
        wait(S); // poczatek sekcji kryt.
        Produkuj();
        N++;
        if( N==1 ) signal(Zwloka);
        signal(S); // koniec sekcji kryt.

        atrapa=321;
        atrapa=321;
        atrapa=321;
      }
    }
    void Konsument()
    {
      int atrapa; string[20] str;

      wait(Zwloka);
      while(1) {
        wait(S); // poczatek sekcji kryt.
        Konsumuj();
        N--;
        signal(S); // koniec sekcji kryt.

        atrapa=123;
        atrapa=321;
        atrapa=123;

        if( N==0 ) wait(Zwloka);
      }
    }
     

    Zadanie 93


    Rozwiąż problem "producenta/konsumenta", dla skończonego bufora. Zakładamy, że jest 1 producent i 1 konsument. Użyj m.in. 2 semaforów ogólnych Pełne i Wolne. "Pelne" będzie interpretowany jako ilość elementów bufora które można skonsumować, "Wolne" będzie interpretowany jako ilość wolnych elementow w buforze. Jest to najbardziej naturalne zastosowanie semaforów ogólnych. Można wykorzystać część kodu z pliku "np03.cm". Aby skontrolować działanie programu należy "produkować" kolejne liczby calkowite, konsument powinien drukowac na ekranie to co "konsumuje", w ten sposób dowiemy się czy wszystko sprawnie działa ...

    Zadanie 93a


    Rozwiąż problem "producenta/konsumenta", dla skończonego bufora. Tym razem zakładamy, że jest kilku producentów i kilku konsumentów. Dodatkowo odpowiedz na pytania:
  • Czy kilku (>1) konsumentów może równocześnie wykonywać swoje operacje na buforze ?.
  • Czy 1 konsument i 1 producent mogą równocześnie wykonywać swoje operacje na buforze ?.

  • Aby skontrolować działanie programu niech każdy producent produkuje kolejne liczby z innego zakresu (np Prod0 od 0, Prod1 od 1000, Prod2 od 2000) a konsumenci powinni wypisywac to co konsumują.

    Zadanie 93b (*)


    Rozwiąż problem "producenta/konsumenta", dla skończonego bufora, przy użyciu semaforów binarnych. Zakładamy, że jest 1 producent i 1 konsument. Można wykorzystać część kodu oraz pomysł z pliku "np03.cm".
     
     
    Zadanie 94

    To jest przykładowe rozwiązanie (np05.cm+np05.inc) problemu "czytelników i pisarzy", w przypadku gdy jest znany rozmiar czytelni. Zwróć uwagę na to, że czytelnia jest "zaimplementowana" w wyraźny sposób (zmienne Ilość*; procedury wchodzenia i wychodzenia z czytelni). Poprawność rozwiązania jest sprawdzana przez proces _Kontrola*, który bez przerwy sprawdza stan czytelni. Nie sprawdza się jednak zagłodzenia procesów. Niewykluczone że wpuszcza się tylko pisarza nr 1, a nie wpuszcza nigdy pisarza nr 2. Dodaj kontrolę ewentualnego zagłodzenia do procesu kontrolnego (powinien on pokazywać które konkretnie procesy są w czytelni). Ponadto dodaj na końcu statystykę demonstrującą ile razy który proces wszedł do czytelni. Tak więc proces kontrolny powinien wyświetlać napisy: Dodatkowo odpowiedz na pytanie: co by się stało gdyby nie było wzajemnego wykluczania pisarzy.

    Zadanie 95


    Rozwiąż problem "pięciu filozofów" używając - m.in.- po jednym semaforze binarnym na każdy "widelec" (semafory te mają być przechowywane w tablicy). Każdy filozof na przemian myśli lub usiłuje zjeść kawałek ryby, do czego potrzebuje 2 widelców. Zauważ, że jeśli wszyscy filozofowie podniosą lewy widelec to wystąpi blokada. Przedstaw ogólne rozwiązanie dla N-filozofów i uzasadnij jego poprawność. Koniecznie dodaj proces kontrolny sprawdzający co w danej chwili robią wszyscy filozofowie (jedzą czy myślą); proces ten powinien wyświetlać następujące napisy:

    Wskazówka: Można uniknąć blokady ograniczając liczbę filozofów którzy chcą równocześnie jeść.
    Uwaga: "Nr" filozofa może być używany wyłącznie do uzyskiwania dostępu do odpowiedniego widelca!


    Zadanie 96 (**)


    Jest to inna wersja rozwiązania problemu "pięciu filozofów". W tej wersji obliczenia są bardziej "równoległe" tj większa ilość filozofów je równocześnie, w porównaniu z rozwiązaniem sugerowanych w zadaniu poprzednim. Każdy filozof próbując przystąpić do jedzenia sprawdza, czy któryś z jego sąsiadów je. Jeśli tak jest, to nasz filozof zostaje "uśpiony" (oczywiście przy pomocy semafora). Gdy pewien filozof skończy jeść, to próbuje obudzić swoich sąsiadów, którzy mogli zasnąć próbując jeść.
  • Filozof nr "i" powinien mieć przyporządkowane:
  •        zmienną "state[i]", przyjmującą wartości "THINKING", "HUNGRY", "EATING"
           semafor "s[i]", zainicjowany zerem.
  • Operacje na zmiennej "state[i]" oraz sprawdzanie stanu sąsiadów powinny być chronione semaforem binarnym "mutex".
  • [przed jedzeniem] Gdy filozof nr "i" próbuje jeść to zaczyna od przypisania "state[i]=HUNGRY", następnie sprawdza sąsiadów: jeśli żaden z nich nie je to zmienia swój stan "state[i]=EATING" oraz wykonuje "signal(s[i])"; następnie wykonuje "wait(s[i])" (jeśli "signal(s[i])" nie zostało wykonane to "wait(s[i])" uśpi naszego filozofa).
  • [po jedzeniu] Gdy filozof nr "i" kończy jedzenie to zmienia swój stan "state[i]=THINKING", a następnie koncentruje swą uwagę na sąsiadach: jeśli któryś z nich został uśpiony w czasie próby jedzenia (tj jego state[.]==HUNGRY) i może jeść, to zmienia jego stan na EATING oraz budzi go przy pomocy "signal(s[.])".

  • Przydatna będzie następująca funkcja:

    Przedstaw ogólne rozwiązanie dla N-filozofów. Proces kontrolny powinien pokazywać co w bieżącej chwili robią wszyscy filozofowie (jedzą czy myślą). Porównaj to rozwiązanie z rozwiązaniem poprzedniego zadania pod kątem równoległości obliczeń (należy porównać wydruki generowane przez oba rozwiązania). Aby zobaczyć efekt zwiększonej "równoległości jedzenia" spowoduj aby jedzenie trwało długo (100 x atrapa=123), a myślenie krótko (2 x atrapa=123).

     
      
     

    Monitory

    Teraz omówimy następne "narzędzie" do rozwiązywania problemów współbieżności - tzw monitor - podobno jest on lepszy od semafora gdyż jest mniejsze prawdopodobieństwo błędnego programowania.
       Dygresja: W jaki sposób s.o. udostępnia monitory skoro wydaje się że monitor stanowi część języka programowania ? Odp: s.o. udostępnia pewne elementarne funkcje, które pozwalają skonstruować monitor wykorzystując jako podstawowe tworzywo np klasy C++ ... (patrz: Linux/Unix i biblioteka Pthread).

    Oto przykład monitora o nazwie "Konta":

    Definicja Monitora.
    Monitor jest zbiorem procedur i zmiennych, spełniającym następujące warunki:

    1. Zmienne są widoczne tylko wewnątrz monitora (tj wewnątrz bloku monitora), natomiast procedury są widoczne na zewnątrz. Tak więc dostęp do zmiennych monitora jest możliwy tylko poprzez procedury monitora.
    2. W danej chwili tylko jeden proces może przebywać w monitorze (tj wykonywać jego procedurę), co umożliwia prostą realizację wzajemnego wykluczania.
    3. Istnieją dwa rodzaje kolejek zawierających wstrzymane procesy: kolejka procesów zewnętrznych (próbujących uruchomić procedurę monitora) oraz kolejki związane ze zmiennymi typu conditon.
    4. Gdy monitor jest zajęty (pewien proces wykonuje jedną z jego procedur) i nastąpi próba wywołania procedury monitora przez inny proces, to ten inny proces zostanie wstrzymany i umieszczony na końcu kolejki procesów zewnętrznych. Gdy monitor zostanie zwolniony, to wtedy wznawiany jest pierwszy proces z kolejki procesów zewnętrznych.
    5. Istnieje możliwość wstrzymywania i wznawiania procesów wewnątrz monitora przy pomocy zmiennych typu condition, na których można wykonywać operacje "waitc()" oraz "signalc()".
    6. Po wykonaniu waitc(c,p) gdzie "c" jest zmienna typu conditon a "p" jest priorytetem (liczbą całkowitą) proces jest usypiany i umieszczany w kolejce związanej ze zmienną "c" (priorytet też jest zapamiętywany). Monitor jest zwalniany, może go zając inny proces. Wykonanie "waitc(c)" to to samo co "waitc(c,10)".
    7. Po wykonaniu signalc(c) budzony jest jeden z procesów z najwyższym priorytetem (najmniejsza liczba), wybrany losowo, w kolejce zmiennej "c", a proces który wykonał "signalc()" jest usypiany i umieszczany na początku kolejki procesów zewnętrznych.
    8. Istnieje możliwość sprawdzenia, czy kolejka zmiennej "c" jest pusta przy pomocy "empty(c)".

    To jest przykład użycia monitora do wzajemnego wykluczania (np06.cm+np02.inc).
    To jest przykład implementacji semafora ogólnego przy pomocy monitora (np07.cm+np02.inc).

    UWAGA 1: powyższa def monitora pochodzi z książki Weiss/Gruźlewski; wydaje się że monitory języka BACI są nieco inne: procesy z "kolejki procesów zewnętrznych" są wybierane losowo tak że nie ma znaczenia czy proces jest umieszczana na końcu czy początku tej kolejki ...

    UWAGA 2: jeśli proces kontrolny ma pokazywać wartości rozmaitych zmiennych (np ilosc czytelników czy pisarzy w czytelni) to te zmienne należy umieścić poza monitorem, przynajmniej w czasie testowanie programu.
     

    Zadanie 99


    Zaimplementuj przy pomocy monitora semafor, w którym wstrzymane procesy nie są wybierane losowo lecz przy pomocy kolejki FIFO (wykorzystaj priorytety !). Dodatkowo wykonaj eksperyment pokazujacy ze to jest faktycznie semafor FIFO - pokaz ze procesy sa wznawiane w tej samej kolejnosci w jakiej byly wstrzymywane (przyda sie procedura which_proc zwracajaca nr procesu ktory ja wywolal).

     
    Zadanie 100


    Rozwiąż problem "producenta/konsumenta" przy pomocy monitora. (Skończony) bufor powinien znajdować się w monitorze. Powinny być dwie zmienne condition, służące do wstrzymywania, odpowiednio, producentów i konsumentów.
     

    Zadanie 101 (*)


    Podaj rozwiązanie problemu "czytelników i pisarzy", bez założenia, że rozmiar czytelni jest znany, używając monitora. W monitorze powinny być 2 zmienne typu "int": IloscPisarzy i IloscCzytelnikow, reprezentujące ilość osób przebywających w czytelni, oraz 2 zmienne typu "condition": Pisarze, Czytelnicy, służące do wstrzymywania osób przed wejściem do czytelni. Dla obu kategorii osób potrzebne będą procedury monitora do wchodzenia i wychodzenia z czytelni.
    Uwaga: w tym zadaniu procedury Czytelnik/Pisarz-Wchodzi/Wychodzi maja inne znaczenie niz w zadaniu 94, przyklad np05.cm; w obecnym zadaniu procedury te sluza jedynie jako sekcja wejsciowa i wyjsciowa uruchamiane przed wejsciem i po wyjsciu osoby z czytelni ...
    Wskazówka: postaraj się zrealizować następujący scenariusz:

    CzytelnikWchodzi: 
    jeśli nie ma pisarza pracującego lub oczekującego to wejdź, w przeciwnym razie czekaj 
    PisarzWchodzi: 
    jeśli w czytelni są czytelnicy lub pisarz to czekaj, w przeciwnym wypadku wejdź 
    CzytelnikWYchodzi: 
    jeśli jestem ostatnim czytelnikiem opuszczającym czytelnie to wpuść pisarza 
    PisarzWYchodzi: 
    jeśli nie ma oczekujących czytelników, to wpuść następnego pisarza, w przeciwnym przypadku wpuść wszystkich czytelników 
    (w czasie wpuszczania czytelników sprawdzaj czy do czytelni nie wszedł pisarz ...)

    Co się może stać jeśli nie uwzględnimy tekstu zapisanego na czerwono ? (opisz dokładnie przeplot zdarzeń prowadzący do błędu).  
     
     

    Mechanizmy niskiego poziomu.

    Będziemy się teraz zajmować konstruowaniem "narzędzi niskiego poziomu" do wzajemnego wykluczania (bardziej elementarnych niż semafory).
     

    Zadanie 102


    (algorytm Petersena)
    Zaprogramuj narzędzie do wzajemnego wykluczania dla 2 procesów, przy pomocy samej tylko "dzielonej" pamięci (w przypadku j. BACI - zmienne globalne).
    W tym celu użyj 2 zmiennych:

    Mamy 2 procesy "P0" i "P1".

  • Gdy proces "Pi" próbuje wejść do sekcji krytycznej to przypisuje flag[i]=1, a następnie czeka aż przeciwnik opuści sekcję krytyczną (pętla while - jest to tzw aktywne czekanie). Gdy to nastąpi, "Pi" wchodzi do sekcji krytycznej.
  • Gdy proces "Pi" wychodzi z sekcji krytycznej to przypisuje flag[i]=0.

  • Zauważ, że z tego co do tej pory zostało powiedziane wynika, że gdyby oba procesy próbowały równocześnie wejść do sekcji krytycznej, to przy nieszczęśliwym zbiegu okoliczności, oba mogłyby zostać zablokowane - i tutaj pojawia się potrzeba użycia zmiennej "turn", proces "Pi" przypisuje jej wartość "i" przed sekcją krytyczną. Procesy P0 i P1 przypisują tej zmiennej 0 i 1, ale oczywiście zmienna może mieć tylko jedną wartość - należy to odpowiednio wykorzystać w aktywnym czekaniu - gdyby oba procesy próbowały równocześnie wejść do sekcji krytycznej, to tylko jeden z nich zostanie zablokowany.
    Zaimplementuj ten algorytm w języku BACI i sprawdź czy rzeczywiście mamy wzajemne wykluczanie i czy żaden z procesów nie jest zagłodzony.  Przygotuj specjalne funkcje do uruchamiania przy wchodzeniu i wychodzeniu ze sekcji krytycznej:

    UWAGA: kolejność modyfikowania zmiennych turn i flag[] przed sek kryt ma duże znaczenie !. Sprawdź eksperymentalnie która kolejność jest prawidłowa (i dołącz wydruki do sprawozdania).