Wątek (informatyka)
Z Wikipedii
Ten artykuł wymaga dopracowania zgodnie z zaleceniami edycyjnymi. Należy w nim poprawić: Brak porządnego wyjasnienia istoty watku; odrożnienia wątku od procesu. Potem przejście do opisu problemu generalnie związanego z programowaniem współbieżnym, nie zaś z samym pojęciem wątku. Dokładniejsze informacje o tym, co należy poprawić, być może znajdziesz na stronie dyskusji tego artykułu. Po naprawieniu wszystkich błędów można usunąć tę wiadomość. |
Wątek (ang. thread) - to jednostka wykonawcza w obrębie jednego procesu, będąca kolejnym ciągiem instrukcji wykonywanym w obrębie tych samych danych (w tej samej przestrzeni adresowej).
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 albo 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.