Wątek (informatyka)

Wątek (ang. thread) – część programu wykonywana współbieżnie w obrębie jednego procesu; w jednym procesie może istnieć wiele wątków.

Proces z dwoma wątkami

Różnica między zwykłym procesem a wątkiem polega na współdzieleniu przez wszystkie wątki działające w danym procesie przestrzeni adresowej oraz wszystkich innych struktur systemowych (np. listy otwartych plików, gniazd itp.) – z kolei procesy posiadają niezależne zasoby.

Ta cecha ma dwie ważne konsekwencje:

  1. Wątki wymagają mniej zasobów do działania i też mniejszy jest czas ich tworzenia.
  2. Dzięki współdzieleniu przestrzeni adresowej (pamięci) wątki jednego zadania mogą się między sobą komunikować w bardzo łatwy sposób, niewymagający pomocy ze strony systemu operacyjnego. Przekazanie dowolnie dużej ilości danych wymaga przesłania jedynie wskaźnika, zaś odczyt (a niekiedy zapis) danych o rozmiarze nie większym od słowa maszynowego nie wymaga synchronizacji (procesor gwarantuje atomowość takiej operacji).

Wątki są udostępniane wprost przez system operacyjny MS Windows, w systemach Linux, BSD i innych dostępna jest biblioteka pthread, dająca jednolity interfejs, ukrywający szczegóły implementacji. W językach programowania używających maszyn wirtualnych (Python, Java itp.) są dostępne również tzw. zielone wątki, które nie są obsługiwane przez system operacyjny, ale samą maszynę wirtualną – to pozwala m.in. na realizację współbieżności nawet wtedy, gdy docelowy system operacyjny nie udostępnia wątków.

Brak synchronizacji między wątkami

edytuj

W systemach wieloprocesorowych, a także w systemach z wywłaszczaniem, wątki mogą być wykonywane równocześnie (współbieżnie). Równoczesny dostęp do wspólnych danych grozi jednak utratą spójności danych i w konsekwencji błędem działania programu.

Podręcznikowy przykład: ciąg instrukcji odczyt-zmiana-zapis.

Załóżmy, że program ma dane do przetwarzania, umieszczone w N pierwszych komórkach tablicy X. Liczba N zapisana jest w odpowiedniej zmiennej. Algorytm przetwarzania mógłby wyglądać następująco:

  1. odczytaj zmienną N i sprawdź, czy jest równa 0
  2. jeśli tak (nie ma danych w X), przejdź do kroku 7.
  3. (tu wchodzimy, gdy N równe 1 lub więcej) odczytaj wartość X[N]
  4. zmniejsz wartość N o 1 (zaznacz, że N-ta dana została już zabrana)
  5. zrób coś z tą odczytaną daną (tu następuje właściwe przetwarzanie)
  6. (dana obsłużona – zajmij się następną) przejdź do kroku 1.
  7. (koniec pracy)

Jest to najprostsza pętla opróżniająca stos X. W środowisku jednowątkowym działa zgodnie z oczekiwaniami, przetwarzając kolejno dane X[N], X[N-1], itd. aż do X[1], po czym zatrzymuje się z zerową wartością zmiennej N.

Jednak w środowisku wielowątkowym dwa równoczesne wątki mogą wykonać się w taki sposób (załóżmy N=2):

wątek 1 wątek 2
krok czynność krok czynność
1. odczyt N=2

2. (nic; N > 0) 1. odczyt N=2
3. odczyt X[N], czyli X[2] 2. (nic; N > 0)


3. odczyt X[N], czyli X[2]
4. zapis N=1



4. zapis N=1
5. (przetwarzanie) 5. (przetwarzanie)
6. (przejście do 1.) 6. (przejście do 1.)

Jak widać, oba wątki pobrały do przetwarzania tę samą daną X[2]. Jeśli nasz program jest systemem księgowym, a w X[2] było zapisane "dokonaj przelewu kwoty xxxx z rachunku nnnn na rachunek mmmm", to przelew zostanie zaksięgowany dwukrotnie.

W dalszym ciągu wykonania tego samego programu możliwy jest również inny przypadek. Przypuśćmy, że wątek nr 1 wolniej przetwarzał X[2] i teraz wątek nr 2 zaczyna kolejny cykl:

wątek 1 wątek 2
krok czynność krok czynność


1. odczyt N=1
1. odczyt N=1 2. (nic; N > 0)


3. odczyt X[N], czyli X[1]
2. (nic; N > 0)



4. zapis N=0
3. odczyt X[N], czyli X[0]


....
....

Pomimo posłużenia się licznikiem N wątek nr 1 usiłuje pobrać nieistniejący element danych spod nielegalnego indeksu – X[0]. W zależności od różnych czynników spowoduje to natychmiastowe awaryjne przerwanie działania programu albo dowolne, nieprzewidywalne zaburzenia (błędy) w dalszym jego działaniu.

Do zapobiegania takim sytuacjom wykorzystuje się mechanizmy synchronizacji wątków: semafory, muteksy, sekcje krytyczne.

Zobacz też

edytuj