# L8 - GRUPA 1 ## Zadanie 1 :::success Autor: Mikołaj Depta ::: ![](https://i.imgur.com/r3lTpqg.png) ![](https://i.imgur.com/kZkAKPo.png) Użycie rejestrów atomowych nie umożliwia nam rozwiązania problemu konsensusu. Rozważmy możliwe operacje na rejestrach RMW, które zmieniają stan. Wybierzmy dowolny rejestr, bez straty ogólności może być to `C`. Będziemy rozpatrywać jak działanie wątków `A` i `B` wpływa na stan widziany przez `C`. Możliwe zachowanie 1. `A` i `B` piszą do rejestrów współdzielonych z `C` - Wówczas niezależnie od kolejności wykonania tych zapisów `C` zobaczy ten sam stan. ![](https://i.imgur.com/EqNKX37.png) 2. `A` i `B` piszą do rejestru, który współdzielą między sobą. Wątek `C` nie ma w ogóle dostępu do tego rejestru, więc ich działania nie zmieniają stanu dla niego widzialnego. ## Zadanie 2 :::success Autor: Jan Dalecki ::: ![](https://i.imgur.com/BFGwu5Q.png) ```java public class RMWConsensus extends ConsensusProtocol { private AtomicInteger[] r = new AtomicInteger(-1)[3]; public T decide(T value) { int i = Thread.getId(); int reg1 = i; //0, 1, 2 int reg2 = (reg1+1)%3; propose(value); double_cAS(r[reg1], r[reg2], -1, i); int val = r[reg1].read(); if(val == i || val != -1){ return proposed[val]; } else { val = r[reg2].read(); return proposed[val]; } } } ``` Zmiany wprowadzone przez `double_cAS` zostaną zobaczone przez wszystkie wątki w tym samym momencie. Załóżmy, że wątek $A$ jako pierwszy wykonał instrukcję `double_cAS`. Rejestry które zostaną zapisane to RAB oraz RAC czyli kolejne wykonanie instrukcji `double_cAS` przez wątki $B$ oraz $C$ się nie powiodą. ## Zadanie 3 :::success Autor: Maksymilian Komarnicki ::: Przypuśćmy, że poziom konsensusu n-ograniczonej funkcji compareAndSet() to n+1. Rozpatrzmy protokół używający rejestru wyposażonego w te funkcję. Protokół taki musi mieć stan krytyczny. BSO wydzielmy z grupy n+1 wątków n wątków oraz rozważmy ich dowolne dwie permutacje. Gdy kolejne wątki w tych permutacjach będą wywoływać ograniczoną funckję compareAndSet(), to rejestr będzie zachowywać się w sposób standardowy. W momencie wywołania compareAndSet() przez (n+1)-szy wątek rejestr wpadnie w stan wadliwy oraz zwróci ⊥. Oznacza to, że ostatni wątek nie będzie w stanie rozróżnić ciągu wywołań funkcji, zatem nie będzie w stanie zadecydować o różnych wartość, z czego wynika sprzeczność. ## Zadanie 4 :::success Autor: PWit ::: **Uwaga: źle!** ```clike= public class Assign23<T> { AtomicReference<T>[] r = (AtomicReference<T>[])new Object[3]; public Assign23(T init ) { for (int i = 0; i < r.length ; i++) r [ i ] = new AtomicReference<T>(init); } public void assign(T v0, T v1, int i0 , int i1) { T t0 = r[ i0 ]. get (); T t1 = r[ i1 ]. get (); r [ i0 ]. compareAndSet(t0, v0); r [ i1 ]. compareAndSet(t0, v0); } public T read(int i) { return r [ i ]. get (); } } ``` Pokażemy, że `assign` jest linearyzowalna. W tym celu rozważmy dowolną współbieżną historię (być może wielu) wywołań `assign` i `read`: 1. wywołanie `assign` współbieżne z wywołaniami `read` (ale nie `assign`!): - :ok: 2. wywołanie `assign` (oznaczmy je przez A) współbieżne z innym wywołaniem `assign` (oznaczmy je przez B) i dowolną liczbą wywołań `read`. Mamy przypadki: a) ustalenia wartości rejestrów w wierszach 13 i 14 powiodły się dla A i B: - linearyzacja w kolejności zmian - zmiany widoczne dla odczytów b) tylko jeden z wierszy 13 i 14 powiódł się dla każdego wątku - oznacza to że A i B wykonywały współbieżnie wiersze (11,14) - linearyzacja w kolejności zmian - wątki wykonujące odczyt po punkcie linearyzacji widzą efekt dwóch rozłącznych zapisów - wątki wykonujące odczyt współbieżnie z zapisami A i B widzą: początkową wartość rejestrów, odczyt wygenerowany przez A lub B (zalezy, którą komórkę odczytają) c) żaden z wierszy 13 i 14 nie powiódł się dla A i B - assign jednego z wątków (np. A) współbieżny z dwoma instancjami assign wykonywanymi przez wątek drugi (B i np. C). Ciekawa sytuacja: - wątek A pisze do komórek 0 i 1 - wątek B pisze do komórek 1 i 0 - dla każdego z wątków dokładnie jeden zapis w wierszach 13 i 14 udaje się Załóżmy że zapis do komórki 0 udaje się dla A, zapis do komórki 1 udaje się dla B. Na początku te dwie komórki mają wartość (init, init) (init, init) (a, init) (a, b) ## Zadanie 5 :::success Autor: Marcin Wróbel ::: ![](https://i.imgur.com/AsitN7z.png) Poziom konsensusu dla obiektów QuasiConsensus wynosi 1. Da się go zaimplementować używając tylko rejestrów atomowych. ```java public class QuasiConsensus { static const int A = 0; static const int B = 1; int propose[] = new int[]{-1,-1}; // rejestry atomowe public int decide(int v) { int me = ThreadID.get(); if (me == A) return decideA(v); return decideB(v); } public int decideA(int v) { if (v == 1) return 1; propose[A] = 0; if (propose[B] == 1) return 1; return 0; } public int decideB(int v) { if (v == 0) return 0; propose[B] = 1; if (propose[A] == 0) return 0; return 1; } } ``` - wątek A proponuje 1 Wątek zawsze A ustali 1. Gdy wątek B zaproponuje 0 i ustali 0 (wątki ustalają różną wartość), albo wątek B zaproponuje 1 i ustali 1 (oba wątki ustalają tą samą wartość 1). - wątek B proponuje 0 Analogicznie jak powyżej wszystko działa - wątek A proponuje 0, wątek B proponuje 1 Mamy pewność, że co najmniej 1 wątek wejdzie do if'a (*). Załóżmy, że jest to wątek A. Stąd wątek A zwróci 1. Jeżeli wątek B zwróci 1 to oba ustalą 1 (poprawna sytuacja). Jeżeli wątek B zwróci 0, to zwrócą rózne wartości (A zwróci 1, B zwróci 0). (*) Co najmniej 1 wątek wejdzie do if'a w decideA, lub decideB `write(propose[A] = 0)` → `read(propose[B] == 1)` `write(propose[B] = 1)` → `read(propose[A] == 0)` BSO załóżmy, że `write(propose[A] = 0)` → `write(propose[B] = 1)` wtedy `write(propose[A] = 0)` → `write(propose[B] = 1)` → `read(propose[A] == 0)`. Wątek A wejdzie do if'a. ## Zadanie 6 :::success Autor: Marcin Sarnecki ::: ![](https://i.imgur.com/eBizqHX.png) Rozważmy drzewo stanów z poprzednich dowodów. Wtedy korzystaliśmy z własności wait-free, aby w nieskończoność nie pozostać w stanie biwalentnym. Niewstrzymywanie (lock-freedom) gwarantuje nam to samo - nie będziemy w jakimś stanie nieskończenie długo, jakiś wątek cały czas postępuje pomimo możliwości uśpienia innych watków. Reszta dowodu jest identyczna ## Zadanie 7 :::success Autor: Julia Matuszewska ::: ![](https://i.imgur.com/RTsoEEq.png) **Algorytm z poprzedniej listy** (została tam też udowodniona jego poprawność) :::spoiler Zad5 lista7 ## Zadanie 5 ``` Autor: Marcin Sarnecki ``` ![](https://i.imgur.com/I5O5J5E.png) ![](https://i.imgur.com/eWrHYnJ.png) * Załóżmy nie wprost, że zwrócono wartość różną od zaproponowanych przez dwa wątki. Zauważmy, że aby do tego doszło, wątek pierwszy musiałby wykonać $return\space proposed[j];$ na niezainicjowanej $proposed[j]$. Dochodzimy do sprzeczności, ponieważ wtedy musiało zajść $position[i] < position[j]$, co implikuje przypisanie wartości $proposed[j]$ * Załóżmy nie wprost, że dwa wątki zwróciły różne wartości. Rozważmy przypadek wykonania ```java if (position[i] < position[j]) // I am behind you return proposed[j]; ``` Jeden z wątków musiał wykonać tego ifa jako pierwszy, wartości $position[]$ mogą się tylko zwiększać, zatem niemożliwe jest, że drugi wątek następnie odczytał swoje position jako mniejsze Rozważmy przypadek wykonania ```java if (position[i] > position[j] + speed[j]) // I am far ahead of you return proposed[i]; ``` Jeden z wątków musiał wykonać tego ifa jako pierwszy, drugi z wątków na pewno wykona drugiego ifa zwracającego $proposed[j]$ * To rozwiązanie nie jest $wait-free$, jeśli wartości $position[]$ są takie same, to wolniejszy wątek może wykonać trzy razy pętlę $while$, następnie szybszy wątek wykona raz pętle i wracamy so sytuacji z równymi $position[]$ ::: ```java= public class ConsensusProposal { Boolean proposed[] = new Boolean[2]; Integer[] speed = new Integer[2]; Integer[] position = new Integer[2]]; public ConsensusProposal(){ position[0] = 0; position[1] = 0; speed[0] = 3; speed[1] = 1; } public Boolean decide(Boolean value) { int i = ThreadID.get(); //0 or 1 int j = 1 - i; proposed[i] = value; while (true) { position[i] = position[i] + speed[i]; if (position[i] > position[j] + speed[j]) // I am far ahead of you return proposed[i]; else if (position[i] < position[j]) // I am behind you return proposed[j]; } } } ``` Korzystamy tylko z rejestrów atomowych więc algorytm nie jest wait-free --- ### Dowód na to, że metoda jest *niehamowana* ###### (jeśli każdy wątek który od pewnego momentu wykonuje metodę przez cały czas w izolacji, zakończy ją) Zauważmy, że w algorytmie dla dwóch wątków od pewnego momentu możemy znaleźć się w sytuacji, że jeden z wątków (niech będzie to A) zakończył działanie funkcji `decide`, a drugi jeszcze nie (niech będzie to B) i jest w stanie ciągłej **izolacji** od tego momentu. Musimy pokazać, że wątek B zakończy funkcję `decide` **2 przypadki:** 1. `position[B] < position[A]` wtedy w kolejnej iteracji pętli `B` zakończy działanie w 21 linijce 2. `position[B] >= position[A]` `A` nie zwiększy swojej pozycji bo już zakończył `decide` więc `B` będzie wykonywać pętlę (zwiększać swoją pozycję) aż do spełnienia warunku w linijce 18 i zakończy działanie Zatem B zakończy swe działanie i jest to metoda niehamowana