.Net: Wątki w C# cz.1

Jak przystało na część pierwszą – będą podstawy. A skoro będą podstawy o wątkach to warto zadać pytanie: Co to takiego wątek? Otóż jest to ścieżka wykonywania programu. Program może być jednowątkowy, czyli mieć jedną ścieżkę wykonywania, lub wielowątkowy – mieć kilka, niezależnych od siebie lub wręcz przeciwnie, ścieżek wykonywania. W przypadku programów wielowątkowych, zasoby takie jak moc obliczeniowa, czy pamięć są przez wątki współdzielone. Nie mniej życie każdego programu zaczyna się od jednego wątku zwanego głównym – czy będzie ich więcej decyduje programista (lub w pewnych przypadkach – jak np. odśmiecanie – tacy spryciarze jak CLR).

Tworzenie wątku

Wątek jest reprezentowany przez klasę Thread z przestrzeni nazw System.Threading. Spójrzmy na poniższy kod:

Jest to program jednowątkowy. Jak nie trudno się domyślić najpierw zostaną w konsoli wypisane kwadraty liczb od 1 do 10, a następnie 10 razy tekst “Hello world of threads”. Zamieszajmy tu jednak nieco, dodając nowy wątek w którym (zamiast w głównym) odpalimy metodę PrintSquares:

W ten sposób sprawiliśmy, że obie metody s ą wykonywane jednocześnie. Za wyświetlanie zdania jest odpowiedzialny wątek główny , a za liczenie i wyświetlanie kwadratów liczb – wątek squaresThread. Teraz nie jesteśmy w stanie przewidzieć jak dokładnie będzie wyglądał rezultat działania programu w konsoli – nawet w przypadku gdy mamy jednordzeniowy procesor, to nasz system operacyjny będzie starał się symulować współbieżność.

Czekanie na koniec pracy wątku

Dorzućmy do powyższego przykładu jeszcze jedną metodę PrintCubes, która jak można się domyślić będzie wypisywała sześciany liczb (również od 1 do 10). Dla ćwiczenia, wywołajmy ją w nowym wątku. Załóżmy też, że metoda PrintCubes musi się wykonać koniecznie dopiero po wykonaniu PrintSquares.

Żeby symulować wytężoną pracę metody PrintSquares dodałem tam, w linijce 23, wywołanie Thread.Sleep(5000) – co jest równoznaczne z wstrzymaniem wykonywania wątku przez określoną, w milisekundach, ilość czasu. Żeby uzyskać efekt jaki nas interesuje – tzn. wykonanie PrintCubes dopiero po zakończeniu działania PrintSquares – dodałem, w linijce 9, wywołanie squaresThread.Join(). Metoda Join powoduje że inne wątki czekają z wykonaniem aż do momentu gdy wywołujący ją wątek skończy pracę.

Kilka słów o blokowaniu wątków

W powyższym przykładzie wykonywanie wątku głównego i wątku cubeThread było zablokowane w poprzez wywołanie metody Join() na wątku squareThread. Zablokowane wątki oddały wykorzystywane zasoby do rozdysponowania, aż do chiwli gdy zostały odblokowane z momentem zakończenia pracy przez squareThread (który swoją drogą był blokowany przez wywołanie Thread.Sleep(500)) – bo to był ich warunek blokady.

Stan lokalny wątku

W powyższym przykładzie każdy z trzech wątków (pamiętajmy o wątku głównym) posiada swój stan lokalny, który w całości jest niezależny od stanów pozostałych wątków. Przejawem owego stanu lokalnego jest zmienna i będąca licznikiem pętli for. Zastanówmy się jakie skutki niósłby za sobą licznik wspólny dla wszystkich wątków.

Stan współdzielony

Popatrzmy na kolejny przykład.

Wyjściem na konsoli, po wykonaniu powyższego kodu, może być coś takiego:

Wydarzyły się tutaj bardzo złe rzeczy, a przyczyną tego był współdzielony,przez wątki, stan. W momencie kiedy jeden wątek odczytywał wartość zmiennej from, inny już ją inkrementował. Powyższy przykłąd jest bardzo prosty, ale ilustruje problemy mogące wyniknąć ze stanu współdzielonego. Należy pamiętać by starać się go jak najczęściej unikać.


Myślę, że powyższe wprowadzenie na ten moment wystarczy. W kolejnym wpisie dotyczącym wątków poświęcę trochę więcej miejsca blokadom, a także podstawowych sposobach zapewnienia bezpieczeństwa w przypadku problemów ze stanem współdzielonym (którego, powtórzę raz jeszcze, powinno się za wszelką cenę unikać :))

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *