Prolem pięciu filozofów (współbieżność)

0

Cześć,

http://wazniak.mimuw.edu.pl/index.php?title=Programowanie_wsp%C3%B3%C5%82bie%C5%BCne_i_rozproszone/PWR_Wyk%C5%82ad_2

Czy poniższy kod jest poprawny?
Problem pięciu filozofów, wersja pierwsza.

 
 const
  N = 5;
var
  widelec: array [0..N-1] of buffer;
  m: integer;                                    { wartość nieistotna }    
process Filozof (i: 0..N-1);
 var
   m: integer;
 begin
   repeat
     myśli;
     GetMessage (widelec[i], m);                 { podniesienie widelca po lewej stronie }
     GetMessage (widelec[(i+1) mod N], m);       { podniesienie widelca po prawej stronie }
     je;
     GetMessage (widelec[i], m);                 { odłożenie widelca po lewej stronie }
     GetMessage (widelec[(i+1) mod N], m);       { odłożenie widelca po prawej stronie }
   until false;
 end

Wydaje mi się, że zamiast dwóch ostatnich GetMessage winno być SendMessage.

Niżej jest co najmniej jeden błąd.
Problem pięciu filozofów, wersja druga.

proces Widelec;
 var
   kto_je, k1, k2: integer;
 begin
   kto_je := 0; k2 := 0; { inicjacja na dowolne wartości, ale tak aby kto_je = k1 }
   repeat
     GetMessage (w[i], k1); 
     if k2 = kto_je then  { przy pierwszym obrocie prawda }
       kto_je := k1                   { zamówienie było od k1 }
     else 
       kto_je := k2;                  { zamówienie było od k2 }
     SendMessage (f[kto_je], i);      { potwierdzenie o dowolnej treści }
     GetMessage (w[i], k2);           { czekamy na komunikat }
   until false
 end;

Łatwo widać, że inicjacja nie jest tak, by kto_je=k1, tylko kto_je=k2.
W jaki sposób powinien wyglądać powyższy algorytm?

Niezależnie od tego, nie rozumiem, jaki jest sens umieszczonej w kodzie instrukcji "if k2 = kto_je then kto je := k1 (...)", skoro za pierwszym razem musi ona zmienić wartość kto_je.

Po drugie, skąd wiadomo, że to k2 będzie trzymało tą wartość, którą chcemy sprawdzić? Tzn.: o ile rozumiem, nie mamy jakichkolwiek gwarancji dotyczących tego, w jakiej kolejności dojdą komunikaty. Załóżmy np., że filozofem, który je jest filozof 2. kto_je i k2 są początkowo ustawione na zero. W jaki sposób kto_je jest inicjowane na rzeczywistą wartość (czyli tutaj 2) jest również dla mnie niezrozumiałe. Załóżmy jednak, że kto_je jest już ustawione na wartość 2. Nadchodzi komunikat, od filozofa 2, który jest umieszczany w k1. Ale ze względu na to, że k1 nie jest nigdy sprawdzane, więc nigdy nie dojdzie do zwolnienia widelca! To nie jest zjawisko zakleszczenia, o którym mowa, to jest inny, dodatkowy błąd w powyższym kodzie.

Z góry dzięki za odpowiedzi,
Pozdrawiam.

0
Arturmax napisał(a):

Cześć,

http://wazniak.mimuw.edu.pl/index.php?title=Programowanie_wsp%C3%B3%C5%82bie%C5%BCne_i_rozproszone/PWR_Wyk%C5%82ad_2

Czy poniższy kod jest poprawny?
Problem pięciu filozofów, wersja pierwsza.

 
 const
  N = 5;
var
  widelec: array [0..N-1] of buffer;
  m: integer;                                    { wartość nieistotna }    
process Filozof (i: 0..N-1);
 var
   m: integer;
 begin
   repeat
     myśli;
     GetMessage (widelec[i], m);                 { podniesienie widelca po lewej stronie }
     GetMessage (widelec[(i+1) mod N], m);       { podniesienie widelca po prawej stronie }
     je;
     GetMessage (widelec[i], m);                 { odłożenie widelca po lewej stronie }
     GetMessage (widelec[(i+1) mod N], m);       { odłożenie widelca po prawej stronie }
   until false;
 end

Wydaje mi się, że zamiast dwóch ostatnich GetMessage winno być SendMessage.

Do tego miejsca masz chyba rację,
co do drugiej części ...

Arturmax napisał(a):

Niżej jest co najmniej jeden błąd.
Problem pięciu filozofów, wersja druga.

proces Widelec;
 var
   kto_je, k1, k2: integer;
 begin
   kto_je := 0; k2 := 0; { inicjacja na dowolne wartości, ale tak aby kto_je = k1 }
   repeat
     GetMessage (w[i], k1); 
     if k2 = kto_je then  { przy pierwszym obrocie prawda }
       kto_je := k1                   { zamówienie było od k1 }
     else 
       kto_je := k2;                  { zamówienie było od k2 }
     SendMessage (f[kto_je], i);      { potwierdzenie o dowolnej treści }
     GetMessage (w[i], k2);           { czekamy na komunikat }
   until false
 end;

Łatwo widać, że inicjacja nie jest tak, by kto_je=k1, tylko kto_je=k2.
W jaki sposób powinien wyglądać powyższy algorytm?

nie prawda,

if k2 = kto_je then  { przy pierwszym obrocie prawda }
       kto_je := k1 

k2 = 0, kto_je = 0 wiec wyrażenie prawdziwe, do kto_je zostaje przypisana wartość k1.

Arturmax napisał(a):

Niezależnie od tego, nie rozumiem, jaki jest sens umieszczonej w kodzie instrukcji "if k2 = kto_je then kto je := k1 (...)", skoro za pierwszym razem musi ona zmienić wartość kto_je.

Zeby w pierwszym obiegu zainicjalizować wartość zmiennej kto_je numerem pierwszego filozofa jaki dobiera sie do widelca... nie wiemy który to będzie wiec nie możemy zakładać, w każdym razie kto_je zostanie ustawione wartością k1, czyli numerem filozofa który pierwszy wysłał żądanie o widelec...

Arturmax napisał(a):

Po drugie, skąd wiadomo, że to k2 będzie trzymało tą wartość, którą chcemy sprawdzić? Tzn.: o ile rozumiem, nie mamy jakichkolwiek gwarancji dotyczących tego, w jakiej kolejności dojdą komunikaty. Załóżmy np., że filozofem, który je jest filozof 2. kto_je i k2 są początkowo ustawione na zero. W jaki sposób kto_je jest inicjowane na rzeczywistą wartość (czyli tutaj 2) jest również dla mnie niezrozumiałe. Załóżmy jednak, że kto_je jest już ustawione na wartość 2. Nadchodzi komunikat, od filozofa 2, który jest umieszczany w k1. Ale ze względu na to, że k1 nie jest nigdy sprawdzane, więc nigdy nie dojdzie do zwolnienia widelca! To nie jest zjawisko zakleszczenia, o którym mowa, to jest inny, dodatkowy błąd w powyższym kodzie.

źle odczytujesz ten kod, może powinieneś popatrzyć w ten sposób:

proces Widelec;
 var
   kto_je, k1, k2: integer;
 begin
   kto_je := 0; k2 := 0; { inicjacja na dowolne wartości, ale tak aby kto_je = k1 }

  { od tego miejsca}
   GetMessage (w[i], k1); 
   kto_je := k1;
   SendMessage (f[kto_je], i);      { potwierdzenie o dowolnej treści }
{  do tego miejsca masz pierwsze wykonanie pętli... }

   repeat
     GetMessage (w[i], k2);          
     GetMessage (w[i], k1); 
     if k2 = kto_je then  { przy pierwszym obrocie prawda }
       kto_je := k1                   { zamówienie było od k1 }
     else 
       kto_je := k2;                  { zamówienie było od k2 }
     SendMessage (f[kto_je], i);      { potwierdzenie o dowolnej treści }     
   until false
 end;

Na poczatku masz GetMessage (w[i], k1); <- to będzie na pewno podniesienie widelca, mało tego na pewno udane, wysyłamy potwierdzenie podniesienia.. i zapamiętujemy "kto je", teraz zaczyna sie petla, na pewno dostaniemy dwa komunikaty, jeden będzie odłożeniem widelca, drugi jego podniesieniem, i teraz jezeli k2 == kto_je, to znaczy że k2 jest tym samym filozofem co je... czyli on ODKŁADA widelec a podnosi go w takim razie filozof k1, w przeciwnym wypadku jest na odwrót, jezeli k1 == kto je to znaczy że k1 odkłąda widelec natomiast k2 je... wiec wysyłamy potwierdzenie do nowej wartości "kto_je" ze widelec udało sie podnieść i teraz mamy petelkę...

1

Jeszcze odniosę sie do tego,

Arturmax napisał(a):

Po drugie, skąd wiadomo, że to k2 będzie trzymało tą wartość, którą chcemy sprawdzić? Tzn.: o ile rozumiem, nie mamy jakichkolwiek gwarancji dotyczących tego, w jakiej kolejności dojdą komunikaty.

Nie do końca prawda, komunikaty do widelca mogą przyjść od tylko dwóch filozofów i na pewno będą sie przeplatać komunikaty, zwolnienia z żądaniem podniesienia powodują to poniższe założenia algorytmu

  • musi wystąpić nowe żądanie podniesienia widelca
  • musi wystąpić informacje o odłożeniu widelca
  • musi wystapić potwierdzenie przez widelec podniesienia.
    Co to oznacza?
    ze każdy filozof przechodzi dla widelca "i" poprzez 3 stany
    p(i) -> o(i) -> z(i)
    p(i) - wyślij żądanie o podniesieniu widelca
    o(i) - czekaj na potwierdzenie
    z(i) - zwolnij widelec
    Dla A tego i B tego filozofa zajdzie :
    pA(i) -> oA(i) -> zA(i)
    pB(i) -> oB(i) -> zB(i)
    Zakładając że filozof A jest pierwszy to na pewno masz trzywarianty
    a) pA(i) ->pB(i) ->oA(i) -> ...
    b) pA(i) -> oA(i) -> pB(i) -> ...
    c) pA(i) -> oA(i) -> zA(i) -> ...
    a) i b) przypadki są w zasadzie równoważne zostańmy przy analizie a) pA(i) -> pB(i) ->oA(i) -> ...
    próba oB(i) skończy sie oczekiwaniem na zA(i) czyli musi zajść :
    zA(i) -> oB(i)
    co daje nam
    pA(i) ->pB(i) ->oA(i) -> zA(i) ->oB(i) -> ..
    na pewno pomiędzy zA(i) ->oB(i) nie wejdzie pA(i) a tym badziej oA(i) dlaczego.. czytaj dalej...

widelec dostaje komunikaty "z" oraz "p" komunikat "o" wysyła sam, jedyne co musi robić widelec to odczytać dwa ostatnie komunikaty(na pewno będzie to z oraz p) sprawdzić który to "z" a który "p", jeżeli kto_je jest równy k1 to k1 jest komunikatem "z" natomiast k2 jest komunikatem "p"
dla powyższego przypadku dwa ostanie to : pB(i) -> ... -> zA(i) wiec czas na wysłanie komunikatu oB(i) nie ma innej możliwości, nawet gdyby już proces A, wysłał kolejne komunikaty to i tak decyzja jest podjęta na podstawie już odebranych dwóch komunikatów wiec nie ma możliwości żeby komunikat pA(i) został odebrany przez wysłaniem oB(i)...

zobaczmy jeszcze przypadek c)
pA(i) ->oA(i) -> zA(i) -> ...
na tym etapie może dojśc do wysłania "p" przez B lub A, jezeli będzie to B to na mamy :
pA(i) -> oA(i) -> zA(i) -> pB(i) dwa ostatnie komunikaty "z" oraz "b" już nam ustalaja który proces jest nastepny do podniesienia widelca
lub
pA(i) -> oA(i) -> zA(i) -> pA(i) nie ma problemu ... widelec wolny mozna od razu znowu go podnieśc jezeli udało sie tak szybko wysłac komunikaty.

Arturmax napisał(a):

Załóżmy np., że filozofem, który je jest filozof 2. kto_je i k2 są początkowo ustawione na zero. W jaki sposób kto_je jest inicjowane na rzeczywistą wartość (czyli tutaj 2) jest również dla mnie niezrozumiałe.

Odpisałem w poprzednim poście.

Arturmax napisał(a):

Załóżmy jednak, że kto_je jest już ustawione na wartość 2. Nadchodzi komunikat, od filozofa 2, który jest umieszczany w k1. Ale ze względu na to, że k1 nie jest nigdy sprawdzane, więc nigdy nie dojdzie do zwolnienia widelca! To nie jest zjawisko zakleszczenia, o którym mowa, to jest inny, dodatkowy błąd w powyższym kodzie.

k1 i k2 nie decydują o zwalnianiu widelców tylko decydują o wyborze filozofa, patrz wyżej o komunikatach "z" oraz "p"

0

Ok, dzięki, wydaje mi się, że rozumiem.
Ale jeszcze dla upewnienia się: to, że pomiędzy zA(i) a oB(i) nie zajdzie pA(i), a tym bardziej oA(i) wynika z poczynionych założeń względem kodu, nie zaś z niego samego, zgadza się (precyzyjniej: jeżeli wiemy, że skoro pB(i) wysłał żądanie, którego jeszcze nie obsłużyliśmy, to najpierw musimy je obsłużyć przy pierwszej nadarzającej się ku temu okazji. Nie interesuje nas natomiast tutaj, w jaki sposób jest to implementowane).

0

Wynika to z samego kodu.

    
     GetMessage (w[i], k2);          
     GetMessage (w[i], k1); 
     if k2 = kto_je then  
       kto_je := k1        
     else 
       kto_je := k2;       
     SendMessage (f[kto_je], i);

Chodzi o to że mamy już odczytane k2 jako pB(i) oraz k1 jako zA(i), następnie widelec podejmie decyzje kogo wpuścić na tej podstawie, ponowny odczyt z kolejki komunikatów odbędzie sie dopiero po wysłaniu oB(i) co znaczy ze nawet gdyby w miedzy czasie przyszedł kolejny komunikat pA(i) zostanie on odczytany już po nadaniu oB(i).
Aha jeszcze jedna ważna uwaga... pA(i) może zostać nadane tylko po zA(i), co chyba jest oczywiste, ale właśnie to dodatkowo upewnia nas że nie ma mozliwości żeby nagle wstrzelił się komunikat pA(i).

0

OK, dzięki. Natomiast teraz mam wątpliwości, co do dalszej części wykładu:

Powróćmy do pierwszej wersji rozwiązania (tej z zakleszczeniem), ale wprowadźmy dodatkowo "bilety do stołu". Filozof, który zechce jeść, musi najpierw otrzymać jeden z czterech (nie pięciu!) biletów do stołu. Potem postępuje tak jak w rozwiązaniu pierwszym.

const
  N = 5;
var
  widelec: array [0..N-1] of buffer;
  bilety : buffer
  m: integer;                                    { wartość nieistotna }                 
 process Filozof (i: 0..N-1);
 var
   m: integer;
 begin
   repeat
     myśli;
     GetMessage (bilety, m);
     GetMessage (widelec[i], m);                 { podniesienie widelca po lewej stronie }
     GetMessage (widelec[(i+1) mod N], m);       { podniesienie widelca po prawej stronie }
     je;
     SendMessage (widelec[i], m);                 { odłożenie widelca po lewej stronie }
     SendMessage (widelec[(i+1) mod N], m);       { odłożenie widelca po prawej stronie }
     SendMessage (bilety, m);       
   until false;
 end

Program główny:

begin
  for i := 1 to N-1 do
    SendMessage (bilety, m);
  for i := 0 to N-1 do
    SendMessage (widelec[i], m);
  cobegin
    for i := 0 to N-1 do
      Filozof (i)
  coend
end.

Tym razem otrzymujemy rozwiązanie poprawne. Zakleszczenia unikamy, gdyż przy stole może być co najwyżej czterech filozofów jednocześnie. Zatem na pewno któryś z nich otrzyma dwa widelce i będzie mógł jeść.

OK, unikamy zakleszczenia, ale skąd mamy gwarancję, że każdy filozof w końcu będzie mógł jeść? Jak pokazać, że nie dojdzie do zagłodzenia któregoś filozofa?

Zwróćmy uwagę, że taka modyfikacja nie zadziałałaby w przypadku poprzedniego rozwiązania. W przeplocie prowadzącym do zagłodzenia bierze udział bowiem trzech filozofów.

W jaki sposób to, że w przeplocie prowadzącym do zagłodzenia bierze udział trzech filozofów implikuje, że taka modyfikacja nie zadziałałaby w przypadku poprzedniego rozwiązania?

Po drugie: czy dobrze rozumiem, że tych trzech filozofów, to filozof głodzony i jego dwaj sąsiedzi?
Prawdę mówiąc, to nie jest dla mnie do końca jasne, w jaki sposób dochodzi do tego głodzenia. To znaczy: załóżmy, że głodzonym filozofem jest filozof 0. Jeżeli tak jest w istocie, to powodem jest to, że nieustannie w stanie głodu jest któryś z dwóch filozofów z numerami 1 i 5. Wcześniej w wykładzie był podany przykład z samochodami na skrzyżowaniu, jedne jadą z zachodu na wschód, drugie z północy na południe. Jeżeli rozwiązalibyśmy to zagadnienie tak, że samochody z grupy północ-południe mogą wjechać na skrzyżowanie wtedy i tylko wtedy, gdy nie mogą na nie wjechać samochody z grupy zachód-wschód, to może dojść do zagłodzenia samochodów pierwszej grupy, gdy cały czas po skrzyżowaniu będą jechały samochody grupy drugiej.

OK. Ale tu mamy inną sytuację, bo filozofowie cały czas w istocie zmieniają swoje stany (myślę/jestem głodny/jem).
Czy chodzi o to, że nie możemy czynić jakichkolwiek założeń dotyczących czasu?

Pomysł rozwiązania tego problemu, który mi się nasunął: zmodyfikować wersję trzecią rozwiązania w ten sposób, że po każdym cyklu są zapamiętywani ci filozofowie, którzy nie jedli. Jeżeli tak było, to w następnym cyklu muszą oni być bezwzględnie wprowadzeni w stan jedzenia, w kolejności od filozofa o najmniejszym numerze, który nie jadł do filozofa o największym numerze? Czy moje rozwiązanie jest poprawne (bo łatwo widać, że na pewno nie jest najkrótsze z możliwych).

0
Arturmax napisał(a):

OK, dzięki. Natomiast teraz mam wątpliwości, co do dalszej części wykładu:

Powróćmy do pierwszej wersji rozwiązania (tej z zakleszczeniem), ale wprowadźmy dodatkowo "bilety do stołu". Filozof, który zechce jeść, musi najpierw otrzymać jeden z czterech (nie pięciu!) biletów do stołu. Potem postępuje tak jak w rozwiązaniu pierwszym.

const
  N = 5;
var
  widelec: array [0..N-1] of buffer;
  bilety : buffer
  m: integer;                                    { wartość nieistotna }                 
 process Filozof (i: 0..N-1);
 var
   m: integer;
 begin
   repeat
     myśli;
     GetMessage (bilety, m);
     GetMessage (widelec[i], m);                 { podniesienie widelca po lewej stronie }
     GetMessage (widelec[(i+1) mod N], m);       { podniesienie widelca po prawej stronie }
     je;
     SendMessage (widelec[i], m);                 { odłożenie widelca po lewej stronie }
     SendMessage (widelec[(i+1) mod N], m);       { odłożenie widelca po prawej stronie }
     SendMessage (bilety, m);       
   until false;
 end

Program główny:

begin
  for i := 1 to N-1 do
    SendMessage (bilety, m);
  for i := 0 to N-1 do
    SendMessage (widelec[i], m);
  cobegin
    for i := 0 to N-1 do
      Filozof (i)
  coend
end.

Tym razem otrzymujemy rozwiązanie poprawne. Zakleszczenia unikamy, gdyż przy stole może być co najwyżej czterech filozofów jednocześnie. Zatem na pewno któryś z nich otrzyma dwa widelce i będzie mógł jeść.

OK, unikamy zakleszczenia, ale skąd mamy gwarancję, że każdy filozof w końcu będzie mógł jeść? Jak pokazać, że nie dojdzie do zagłodzenia któregoś filozofa?

Tutaj odp. jest prosta, stołuje sie zawsze 4 filozofów, co oznacza ze przynajmniej jeden z nich bedzie dał rade podnieść dwa widelce i zjeść. Taki najedzony filozof odda bilet, który powinien z dużym prawdopodobieństwem wziąć INNY filozof, ponieważ istnieje duże prawdopodobieństwo że ten inny filozof już stoi w kolejce po bilet, kolejka jest FIFO więc gwarantuje że filozof, który dopiero co opuszcza stół nie wywłaszczy go z tej kolejki. Jeżeli "i"-ty filozof opuszcza stół(oddaje bilet) to odblokuje przynajmniej jednego ze swoich sąsiadów, który będzie mógł się najeść i odblokować ponownie jednego ze swoich sąsiadów itd. Nie powinno też dojść do sytuacji ciągłej rotacji tych samych filozofów z zagłodzeniem jednego bo każdy z nich po prostu kolejkuje sie na widelcu wiec nowy filozof który dop. co dostał bilet nie wyprzedzi w dostępie do widelca filozofów posiadających bilet dłużej.

Co do drugiej części posta, musiałbym trochę dłużej nad tym pomyśleć a obecnie nie mam na to czasu, jak tylko będę miał go więcej postaram się pomyśleć nad sytuacją zagłodzenia. Podejrzewam jednak że kluczem jest tutaj różnica w dostępie do widelca... a ściślej :
".. bo każdy z nich po prostu kolejkuje się na widelcu wiec nowy filozof który dop. co dostał bilet nie wyprzedzi w dostępie do widelca filozofów posiadających bilet dłużej. " Podejrzewam że tutaj problem stanowi właśnie to że filozof musi oczekiwać na potwierdzenie i może dojść do sytuacji że jeden z filozofów nigdy nie dostanie potwierdzenia.

4

Proponuję algorytm, w którym każdy filozof będzie miał z góry przyznaną siłę i wytrzymałość, a o widelce będą się bić. Filozof o zerowym HP na pewien czas odchodzi od stołu, żeby nabrać sił.
Jeśli się okaże, że jeden cały czas wygrywa (jest zbyt silny) może zostać dźgnięty w oko, co wyłączy go na dobre, ale czasowo niedostępny będzie też widelec (trzeba go umyć).

0

xxx_xx_x, OK, dzięki, chociaż powyższy algorytm nadal nie jest dla mnie do końca jasny.
Ale o ile rozumiem, to jego działanie opiera się na fakcie, że z założeń wynika, że SendMessage(bilety, m) zostanie w końcu za którymś razem obsłużone, a mechanizm biletów zapewnia obsłużenie każdego filozofa, tak? Nadal nie mamy natomiast jakiejkolwiek gwarancji sprawiedliwości czasowego rozdzielania (tzn. jeżeli dany filozof zażądał rezerwacji biletu, to ta rezerwacja na pewno nastąpi, natomiast nie można nic powiedzieć na temat, czy będzie to w kolejce 2, 5, czy 100).

Nie rozumiem natomiast, dlaczego SendMessage(bilety, m) występuje zarówno w kodzie górnym, jak i w głównym programie.

Nadal jestem zainteresowany, jaka jest odpowiedź na pozostałe pytania :).

Pozdrawiam.

1 użytkowników online, w tym zalogowanych: 0, gości: 1