# L2 - GRUPA 1 ## Zadanie 1 :::success Autor: Mikołaj Depta ::: Poniższy algorytm ma w zamierzeniu implementować interfejs Lock dla dowolnej liczby n wątków. Czy ten algorytm spełnia warunek 1. wzajemnego wykluczania (ang. mutual exclusion), 2. niezagłodzenia (ang. starvation–freedom), 3. niezakleszczenia (ang. deadlock–freedom)? Zmiennymiwspółdzielonymi przez wątki są `turn` i `busy`. ```java= class Foo implements Lock { private int turn; private boolean busy = false; public void lock() { int me = ThreadID.get(); /*get id of my thread*/ do { do { turn = me; } while (busy); busy = true; } while (turn != me); } public void unlock() { busy = false; } } ``` ## 1. Wzajemne wykluczenie Założmy, nie wprost, że dwa wątki dostały się do sekcji krytycznej. Z kodu oznacza to, że zaszły następujące relacje między zdarzeniami: `Write_B(turn=B) → Read_B(busy==false) → Write_B(busy=true) → Read_B(turn==B)` `Write_A(turn=A) → Read_A(busy==false) → Write_A(busy=true) → Read_A(turn==A)` Oraz z fakty, że oba wątki są w sekcji krytycznej, wiemy że: `Read_B(turn==B) → Write_A(turn=A)` `Read_A(turn==A) → Write_B(turn=B)` Wówczas: `Read_B(turn==B) → Write_A(turn=A)` `Write_A(turn=A) → Read_A(busy==false) → Write_A(busy=true) → Read_A(turn==A)` `Read_A(turn==A) → Write_B(turn=B)` `Write_B(turn=B) → Read_B(busy==false) → Write_B(busy=true) → Read_B(turn==B)` Mamy cykl co jest sprzeczne z antyzwrotnością relacji `→`. ## 2. niezagłodzenia Może dojść do zagłodzenia w następującym scenariuszu. ``` A: lock() Write_A(turn=A) idzie spać B: lock() Write_B(turn=B) Read_B(busy==false) Write_B(busy=true) // teraz A może zostać obudzony ale i tak będzie kręcił się w pętli Read_B(turn==B) wykonuje sekcję krytyczną unlock() Write_B(busy) znowu wykonaj lock() do momentu Write_B(busy=true) ``` Nie ma żadnego mechanizmu gwarantującego, że wątek `B` w końcu ustąpi `A`. ## 3. niezakleszczenia ``` A: lock() Write_A(turn=A) Read_A(busy==false) Write_A(busy=true) idzie spać B: lock() Write_B(turn=B) Read_B(busy==true) zapętla się idzie spać A: kontunuacja lock() Read_A(turn==B) wraca do Write_A(turn=A) Read_A(busy==true) pętli się Mamy deadlock ``` Deadlock występuje tutaj ponieważ, wątek oznajmia, że sekcja krytyczna jest zajęta (`busy==true`) przy czym nie ma gwarancji, że faktycznie do niej wejdzie a co za tym idzie, potencjalnie z niej nie wyjdzie i nie wywoła `unlock()`. ## Zadanie 2 :::success Autor: Marcin Sarnecki ::: ```java= public void lock() { flag[i] = true; victim = i; while (flag[j] && victim == i) {}; } public void unlock() { int i = ThreadID.get(); flag[i] = false; int j = 1 - i; while (flag[j] == true) {} } ``` **Zakleszczenie** Rozważmy możliwość zakleszczenia w miejscu dodanych linijek. Założmy, że doszło do zakleszczenia. Bez straty ogólności założmy, że wątek 0 jest w funkcji unlock. Wtedy flag[0] = false, zatem wątek 1 nie zakleszczy się w funkcji lock() w linijce 4. W przypadku, w którym oba wątki znajdą się w lock() nie dojdzie do zakleszczenia ze względu na zmienną victim. W przypadku, w którym oba wątki będą w unlock() również nie dojdzie do zakleszczenia ze względu na linijki 9 i 11. **Głodzenie** Głodzenie występuje. Rozważmy przypadek w którym wątek 0 właśnie skończył sekcję krytyczną, ustawia flag[0]=false, zaczyna czekać w linijce 11 (wątek 1 czeka na wejście do sekcji krytycznej, więc flag[1]=true) i oddaje procesor wątkowi 1. Wątek 1 wchodzi do sekcji krytycznej, wykona ją i przechodzi do sekcji unlock. Przechodzi przez nią bez problemu, ponieważ flag[0]=false. Może więc wejść ponownie do funkcji *lock*, ustawić swoją flagę na true i wejść do sekcji krytycznej. Wątek 1 może teraz cały czas przechodzić przez unlock() i blokować wątek 0. ## Zadanie 3 :::success Autor: Wiktor Bukowski ::: **Właśność r-ograniczonego czekania** oznacza, że wątek, który wykonał sekcję wejściową będzie "wyprzedzony" co najwyżej r razy przez taki wątek, który wykonał tę sekcję po nim. **Sekcja wejściowa** to część, w której ustawiana jest flaga oraz victim. **Sekcja oczekiwania** to pętla ```while(flag[j] && victim == i) {};``` Algorytm Petersona: ```java= public void lock() { flag[i] = true; victim = i; while (flag[j] && victim == i) {}; } public void unlock() { flag[i] = false; } ``` Oznaczmy jako A ten z wątków, który jako pierwszy wykonał sekcję wejściową. Drugi z nich oznaczmy jako B. Załóżmy, że B jako pierwszy wykonał sekcję krytyczną. $D_A \rightarrow D_B$ $CS_B \rightarrow CS_A$ $D_A \rightarrow W_A \rightarrow CS_A$ $D_B \rightarrow W_B \rightarrow CS_B$ Wtedy z przechodniości $\rightarrow$: $D_A \rightarrow D_B \rightarrow CS_B \rightarrow CS_A$ Jeśli B jako drugi wykonał sekcję wejściową, to ustawił zmienną `victim` na samego siebie, przez co musiał potem czekać w sekcji oczekiwania do wykonania przez A sekcji krytycznej i zmiany jego flagi. W związku z tym B nie mógł wykonać sekcji krytycznej przed A. ## Zadanie 4 :::success Autor: Marcin Wróbel ::: ![](https://i.imgur.com/X52gW04.png) **a) wzajemne wykluczanie** Dowód indukcyjny, teza: W danym wierzchołku co najwyżej jeden wątek zajął lock'a. n=liczba wątków Dla **n==2** Dla liści jest to prawdziwe, ze względu na to, że algorytm Petersona działa. Dla **n>2** Rozpatrzmy korzeń v. W dzieciach wierzchołka v co najwyżej jeden wątek dostał lock'a z założenia indukcyjnego. Więc co najwyżej 2 wątki mają dostęp do wierzchołka v. Stąd z działania algorytmu Petersona w wierzchołku też co najwyżej jeden wątek dostał lock'a wierzchołka v. **b) zakleszczenie** Dowód indukcyjny, teza: W danym wierzchołku jeżeli w jego poddrzewie jest wątek chcący uzyskać dostęp do sekcji krytycznej to zajmie lock'a w danym wierchołku. Dla **n==2** Dla liści jest to prawdziwe, ze względu na to, że algorytm Petersona działa. Dla **n>2** Rozpatrzmy korzeń v. W dzieciach wierzchołka v wiemy, że jakiś wątek uzyska lock'a z założenia indukcyjnego. Łącząc to z tym, że wiemy, że algorytm Petersena działa, jakiś wątek dostanie lock'a w wierzchołku v. Stąd jakiś wątek zajmie lock'a w korzeniu. Wiemy, więc, że ten wątek dostanie dostęp do sekcji krytycznej. **c) zagłodzenie** Wiedząc, że algorytm Petersona nie zagładza żadnego wątku, wiemy, że żadne poddrzewo wierzchołka nie zostanie zagłodzone. Stąd indukcyjnie Dla **n==2** nie nastąpi zagłodzenie, ze względu na to, że algorytm Petersona działa. Dla **n>2** Korzeń v ma dwoje dzieci, w których żaden wątek nie zostaje zagłodzony, z założenia indukcyjnego. Z tego, że algorytm Petersona działa żadne poddrzewo nie zostanie zagłodzone, więc żaden wątek z obu poddrzew nie zostanie zagłodzony. ## Zadanie 5 :::success Autor: Michał Hetnar ::: 1.Czy istnieje taka liczba r, być może zależna od n, że algorytm tree-lock spełnia własność r-ograniczonego czekania (ang. r-Bounded Waiting)? Jako sekcję wejściową (ang. doorway section) algorytmu przyjmij fragment kodu przed pętlą while zamka w odpowiednim liściu. Jeżeli nie założymy że wątkom przydzielany czas jest równomiernie: możemy mieć sytułację że wątek który przejdzie doorway section w swoim liściu nie dostanie wiecej czasu procesora, nic nie powstrzyma procesów wyżej od przejścia przed nim nieskończoną liczbę razy. Możemy założyć że wątkom przydzielany czas jest równomiernie: ![](https://i.imgur.com/XuvMKcq.jpg) w tym przypadku przed 8 wykonały się wszystkie wątki ale 8 i 7 (bo musiała być przed nią ) nie liczymy wiec R= N-2 2. Pokaż, być może modyfikując nieco oryginalny algorytm, że założenie o numerach wątków w poprzednim zadaniu może być łatwo usunięte. Każdy z zamków traktuje jeden z rywalizujących o niego wątków jako wątek o numerze 0 a drugi jako wątek 1. ```java= public void lock() { flag[i] = true; victim = i; while (flag[i] && victim == i) {}; } public void unlock() { flag[i] = ``` ```java= int flags = 0; public void lock() { flags += 1; victim = ThreadID.get(); while (flags==2 && victim == ThreadID.get()) {}; } public void unlock() { flags -=1; ``` ## Zadanie 6 :::success Autor: Wojciech Pokój ::: W dobrze zaprojektowanym programie wielowątkowym rywalizacja o zamki powinna być niewielka. Najbardziej praktyczną miarą wydajności algorytmu implementującego zamek jest więc liczba kroków potrzebna do zajęcia wolnego zamku, jeśli tylko jeden wątek się o to ubiega. Poniższy kod jest propozycją uniwersalnego wrappera, który ma przekształcić dowolny zamek na zamek wykonujący tylko stałą liczbę kroków w opisanym przypadku. Czy ten algorytm spełnia warunek a) wzajemnego wykluczania, b) niezagłodzenia, c) niezakleszczenia? Załóż, że oryginalny zamek spełnia te warunki. ``` java= class FastPath implements Lock { private Lock lock; private int x, y = -1; public void lock() { int i = ThreadID.get(); x = i; // I’m here while (y != -1) {} // is the lock free? y = i; // me again? if (x != i) // Am I still here? lock.lock(); // slow path } public void unlock() { y = -1; lock.unlock(); } } ``` **Wzajemne wykluczenie** Własność nie jest spełniona Przykład dla dwóch wątków: Oba wątki dochodzą do linii 8 jednocześnie. w tym miejscu x ma już jakąś ustaloną wartość (wskazuje na jeden z wątków), więc jeden wątek zajmnie kłódkę (lock.lock), a drugi wyjdzie z zamka, w efekcie czego oba wątki uzyskają dostęp do sekcji krytycznej **Niezagłodzenie** Własność nie jest spełniona i jest już widoczna dla dwóch wątków: Wystarczy że powolny wątek utknie w linijce 7 w czasie gdy drugi wątek będzie bardzo szybko wchodzić i wychodzić do i z sekcji krytycznej **Niezakleszczenie** Własność jest spełniona Załóżmy nie wprost że któryś z wątków zablokował się na pętli w linii 7, tj. zmienna y ma wartość różną od -1 ale żaden wątek nie wykonuje sekcji krytycznej. Ale żeby tak było któryś z wątków musiał wcześniej wyjść z sekcji krytycznej więc ustawił y na wartość -1. Sprzeczność ## Zadanie 7 :::success Autor: Jan Dalecki ::: ![](https://i.imgur.com/p9Q6t7h.png) $X$ - zbiór wszystkich wątków **a)** Załóżmy, że co najmniej 2 wątki otrzymały wartość STOP. Z kodu: $$ read_A(last == A) \rightarrow return_A(STOP) $$ $$ write_A(last = A) \rightarrow read_A(goRight == false) \rightarrow write_A(goRight = true) $$ $$ write_B(last = B) \rightarrow read_B(goRight == false) \rightarrow write_B(goRight = true) $$ Założenia: $$ \forall C \in X\backslash A \ read_A(last == A) \rightarrow write_{C}(last = C) $$ $$ \forall C \in X\backslash B \ read_B(last == B) \rightarrow write_{C}(last = C) $$ Z założeń wynika, że procesy nie mogą wykonać zapisau pod `last` dopóki dany proces nie odczyta wartości `last`. Dany proces zmieni wartość `goRight` na `true`. Zmienna `goRight` nie będzie miała wartości `false` dla drugiego procesu. **b)** Załóżmy, że wszystkie wątki otrzymały wartość DOWN. Z kodu $$ read_X(last \not= X) \rightarrow return_X(DOWN) $$ Założenia $\forall A \in X$, $\exists B \in X\backslash A$: $$ write_A(last = A) \rightarrow write_{B}(last = B) \rightarrow read_A(last \not= A) $$ staje się jasne, że to nie może zajść dla procesu który jako ostatni wykona podstawienie pod `last`. c) Załóżmy, że wszystkie wątki otrzymały wartość RIGHT. Z kodu: $$ read_X(goRight == true) \rightarrow return_X(RIGHT) $$ $$ read_A(goRight == false) \rightarrow write_A(goRight = true) \rightarrow return_A(STOP) $$ lub $$ read_A(goRight == false) \rightarrow write_A(goRight = true) \rightarrow return_A(DOWN) $$ Założenia: $$ \forall A \in X \ read_A(goRight == true) \rightarrow return_A(RIGHT) $$ $$ \exists B \in X \ read_B(goRight == false) \rightarrow write_B(goRight = true) $$ Widzimy, że $B$ nie zwróci RIGHT. ## Zadanie 8 :::success Autor: Bartosz Głowacki ::: ![](https://i.imgur.com/3pl8zDx.png) Wiemy że: 1) w pierwszym wierzchołku na początku znajduje się N wątków 2) w każdym wierzchołku co najwyżej k-1 (gdzie k to liczba wątków w tym wierzchiłku) wątków idzie na prawo to samo dotyczy lewa 3) na każdym poziomie drzewa sumaryczna liczba wątków wynosi maksymalnie N (wynika to z fakty że w każdym kroku wątek schodzi dokładnie poziom w drzewie niżej) 4) gdy w wierzchołku znajduje się 1 wątek dostanie on STOP ### Policzmy więc ile maksymalnie wątków może znaleźć się w każdym wierzchołku W wierzchołku $0$ znajduje się $N$ wątków #### Weźmy teraz wierzchołki $2, 5, 9, ...$ (wierzchołki wysunięte maksymalnie w lewo) Wątki w tych wierzchołkach mogą znaleźć się tam tylko idąc w lewo z wierzchołka wyżej a z **(2)** wiemy że maksymalna liczba wątków będzie w każdym kroku zmniejszać się o jeden mamy zatem maksymalnie $N-1$ wątków w wierzchołku $2$ maksymalnie $N-2$ wątków w wierzchołku $5$ ... #### Weźmy teraz wierzchołki $1, 3, 6, ...$ (wierzchołki wysunięte maksymalnie w prawo) Dla nich możemy przeprowadzić analogiczne co wyżej rozumowanie #### Weźmy teraz wierzchołek 4 mogą do niego wejść wątki z wierzchołków $1$ i $2$ załóżmy że w wierzchołku $1$ mamy $K_1$ wątków a w wierzchołku 2 $K_2$ wątków. wiemy że $K_1, K_2 \in [0, N-1]$ z **(2)** wiemy że do wierzchołka 4 trafi co najwyżej $max(K_1-1, 0) + max(K_2-1,0)$ wątków z **(3)** wiemy że $K_1+K_2 \le N$ w takim razie w wierzchołku $4$ znajduje się maksymalnie $N-2$ wątków #### Rozumowanie indukcyjne widzimy więc że na $0$ poziomie drzewa wierzchołki mają maksymalnie $N$ wątków na pierwszym $N-1$ wątków na drugim $N-2$ możemy więc wysnuć hipotezę że na wysokości $k$ drzewa w każdym wierzchołku może znaleźć się maksymalnie $N-k$ wierzchołków żałóżmy więc że na poziomie $k$ mamy $N-k$ maksymalnie wątków ma wierzchołek i pokażmy że na poziomie $k+1$ mamy maksymalnie $N-k-1$ wątków na wierzchołek zatem weźmy dowolny wierzchołek $c$ i wierzchołki $a$, $b$ z krótych można przejść do wierzchołka $c$. Wierzchołki $a$ i $b$ mają odpowiednio $K_a$, $K_b$ wątków mamy zatem: wiemy że $K_a, K_b \in [0, N-k]$ z **(2)** wiemy że do wierzchołka $c$ trafi co najwyżej $max(K_a-1, 0) + max(K_b-1,0)$ wątków z **(3)** wiemy że $K_a+K_b \le N$ w takim razie w wierzchołku $c$ znajduje się maksymalnie $N-k-1$ wątków co dowodzi tezy #### Konkluzja wiemy zatem że na poziomie $k$ drzewa wierzchołki mają co najwyżej $N-k$ wątków można zatem zauważyć że na poziomie $N-1$ drzewa mamy maksymalnie $1$ wątek a z **(4)** wiemy że w takim razie jeśli wątek tam dojdzie dostanie STOP 1) w takim razie każdy wątek dostanie kiedyś STOP ponieważ prędzej czy później natrafi na wierzchołek w którym będzie jedyny lub dostanie STOP wcześniej 2) zatem sumując odpowiednio liczbę wierzchołków z każdego poziomu liczbę wierzchołków można ograniczyć do $1 + 2 + \ldots + N=\frac{N+1}{2} \times N$ ![](https://i.imgur.com/t4pDt84.png)