Wątki – wielowątkowość oraz wyścig (race condition)

Wątki – wielowątkowość oraz wyścig (race condition)

Cześć!

Do tej pory pojawiły się dwa posty o tematyce wątków. Omówione zostały między innymi:

  • definicja wątku (teoria oraz praktyka)
  • tworzenie wątku za pomocą dziedziczenia po klasie Thread oraz za pomocą implementacji interfejsu Runnable / Callable
  • Runnable oraz Callable – omówienie oraz różnice
  • cykl życia wątku
  • przerywanie wątku
Post nr 1 (definicja wątku, tworzenie wątku) – kliknij tutaj
Post nr 2 (Runnable oraz Callable, cykl życia wątku oraz przerywanie wątku) – kliknij tutaj

Zgodnie z zapowiedziami, dzisiaj chciałbym poruszyć szerzej temat wielowątkowości. Omówimy przede wszystkim pojęcie wielowątkowości w teorii oraz w praktyce, pułapkach z tym związanych oraz jak sobie z tym radzić. Do dzieła!

Definicja (wielowątkowość)

Wielowątkowość (ang. Multhithreading) oznacza, że podczas wykonywania programu, różne wątki mogą być wykonywane w tym samym czasie na różnych procesorach. Sterowanie programu w takich przypadkach przebiega współbieżnie.

Wyścig – race condition

     Jest to jeden z najczęściej spotykanych problemów jeśli chodzi o wielowątkowość. Do takiej sytuacji zachodzi w sytuacji, kiedy kilka wątków jednocześnie modyfikuje jedną zmienną, która do takiej zmiany nie jest przystosowana. W dowolnym czasie algorytm planowania wątków może przełączać się między wątkami, nigdy nie znamy dokładnej kolejności. Dlatego wynik zmiany jakiejś zmiennej może zależeć od algorytmu planowania wątków, czyli prościej mówiąc: dwa wątki „ścigają się” w celu zmiany jakiejś danej. 

Race condition – przykład z życia wzięty

     Wyobraźmy sobie, że planujemy iść na mecz. Mecz rozpoczyna się o 15:00, natomiast o 14:00 dzwonimy do kasy biletowej z zapytaniem, czy są dostępne jeszcze bilety, na co pracownik klubu odpowiada, że bilety sa jeszcze dostępne. Ubieramy się, docierając kilka minut przed 15:00 na mecz i okazuje się, że biletów już nie ma. Problem polega na czasie między sprawdzeniem a działaniem. Zapytaliśmy o godzinie 14:00 a działaliśmy kilka minut przed 15:00. W między czasie inne osoby wykupiły bilety. I to jest warunek wyścigu, w szczególności „sprawdź, a następnie działaj”. 

Race condition – przykład z kodem

Spójrzmy na praktyczny przykład: 

Jeżeli mamy pięć wątków jednocześnie wykonujących ten kod, wartość x nie skońzy się na 10 000 000. W rzeczywistości zmienna zmieniałąby się przy każdym uruchomieniu. Wytłumaczenie jest proste: aby każdy wątek mógł zwiększyć wartość x, tłumacząc to na pseudokod, trzeba wykonać następujące czynności:

  • Pobrać wartość x
  • Dodać 1 do wartości x
  • Zapisać tą wartość na x

Dowolny wątki, w dowolnym etapie wykonywania programu mogą na siebie nachodzić. Stan zmiennej „x” może zostać zmieniony przez inny wątek podczas gdy inne odczytuje „x” i jest on ponownie zapisany. Czyli wątek pobiera wartość „x”, ale jeszcze jej nie zapisał.Inny wątek w tym samym czasie może pobrać tą samą wartość „x” (ponieważ wcześniejszy wątek nie zdążył zmienić tej zmiennej), a następnie oba zapisywałyby tę samą wartość (x +1) ponownie w zmiennej x.

Prościej mówiąc:

Wątek 1: odczyt wartości x, wartość wynosi 6

Wątek 1: dodaj 1 do zmiennej x, wartość wyniesie teraz 7

Wątek 2: odczyt wartości x, wartość wynosi obecnie 6

Wątek 1: nanosi wartość 7 na zmienną x

Wątek 2: dodaje 1 do zmiennej x, wartość wyniesie teraz 7

Wątek 2: nanosi wartość 7 na zmienną x

W tym przypadku, najlepszym rozwiązaniem będzie zastosować jakiś mechanizm blokujący wartość x podczas dodawania „1” do jego wartości i odblokowywania go po wykonaniu zadania.

Synchronized jako lek na race condition

     W celu uniknięcia wyścigu, bardzo dobrym rozwiązaniem wydaje się synchronizacja wątków. Zanim przejdę do szczegółowego omówienia synchronizacji wątków, przejdźmy przez przykład praktyczny:

Jeśli nie zsynchronizujemy wątków, podczas np. operacji wypłaty pieniędzy z bankomatu możemy skończyć z sztucznymi stworzonymi pieniędzmi (wypłacone lecz niepobrane z konta) lub z debetem na koncie (pobranie pieniądze z konta ale niewypłacone). 

Na ratunek przychodzi słowo kluczowe „synchronized”, które można założyć na całej metodzie lub fragmencie kodu. Spójrzmy na poniższy przykład:

Jeśli dwa wątki będą próbowały wykonać naszą metodę „transferujPieniadze” może dojść do wyżej wspomnianych sytuacji. Zarówno wątek X jak i wątek Y mogą w tym samym czasie sprawdzać warunek „if” w tym samym momencie i wejść do bloku kodu związanego z przesyłaniem tych pieniędzy. Może okazac się, że pieniędzy na koncie było tylko tyle, by wystarczyło na jeden przelew, wtedy może skończyć się to z błędnym stanem konta – ilość pieniędzy na tym koncie będzie ujemna.

W celu uniknięcia takich sytuacji wystarczy, że dodamy słowo kluczowe „synchronized” które możemy założyć na całą metodę. Pozwoli to wykonać ten kod tylko jednemu wątkowi w jednej chwili. Kiedy dwa wątki będą próbowały wykonać ten kod jednocześnie, będą musiały zaczekać, aż jeden wątek skończy pracę. Wykonają się jeden po drugim.  Nasza metoda będzie wyglądała tak:

Należy pamiętać, że synchronized można dodać jako słowo kluczowe w sygnaturze metody (jak wyżej) lub jako blok kodu, w tym przypadku:

Pułapki związane z synchronized

     Czasami zdarza się, że programiści zaczynają synchronizować wszystko co możliwe. Pamiętajmy jednak , że cały kod, który znajduje się w bloku synchronized w danym momencie może zostać uruchomiony przez jeden wątek. W efekcie dany fragment kodu traci możliwość uruchomienia na kilku procesorach równocześnie. Synchronizujmy więc z głową. 

Podsumowanie

     Uważam, że dość szczegółowo przebrnęliśmy przez pojęcie wielowątkowości, wyścigu oraz tym, jak radzić sobie z tymi wyścigami. Jak widać, wątki są trochę skomplikowane i czymś bardzo normalnym jest, że na początku swojej kariery z programowaniem mogą wydawać się czarną magią albo czymś, co teoretycznie może być bezużyteczne. Im bardziej skomplikowane zadania będziemy wkonywać, tym będziemy świadomi pojęcia wielowątkowości. Oczywiście to nie koniec serii o wątkach. W kolejnym programistycznym poście chciałbym opisać takie tematy jak: locki, semaphory, latche oraz barriery

Pozdrawiam serdecznie,
biegajacyprogramista.pl

Dodaj komentarz

16 + eighteen =

Close Menu