Kurs C
menu C#
Wątki i wielobieżność w C |
W poprzednim artykule opisałem wątki w języku C. Czyli pokazałem jak odpalić dwie funkcje pracujące równolegle obok siebie. Wszystko będzie ładnie działało do momentu, gdy te zmienne nie będą pracowały na tych samych zmiennych. Co się stanie, gdy stworzymy dwa wątki na tej samej funkcji, która będzie odnosiła się do tej samej zmiennej globalnej? Czy kod będzie pracował prawidłowo? Aby przedstawić taką sytuację zmodyfikowałem kod z poprzedniego programu. W tym programie mamy jedną główną zmienną globalną licznik = 2mln . Chcemy stworzyć funkcję, która będzie zmniejszała licznik o 1 w pętli i wykona się ona milion razy. Czyli odpalając dwa razy taką funkcję zmniejszymy nasz licznik do 0. Nasz program będzie wyglądał następująco: Czyli odpalamy 2 wątki wywołujące funkcję wykonaj1(). Teoretycznie powinniśmy na końcu dostać 0. Ale w naszej konsoli ukazuje się coś takiego: Po wykonaniu dwóch wątków nasz licznik wskazuje na jakąś losową zmienną od 0 do 2mln. Dlaczego? Jak zmodyfikować ten kod, aby uzyskać dobry wynik? Odpowiedź jest prosta. Dwa te same wątki stworzone są na kopi tej samej funkcji, czyli pobierają dane z tej same zmiennej licznik w różnych odstępach czasu. Gdy jedna funkcja rozpoczęła odejmowanie inna dopiero mogła pobrać dane i zresetować licznik pierwszej funkcji. Jak więc poradzić sobie w takiej sytuacji? Należy wprowadzić sygnalizację. Czyli najpierw pozwolić wykonać działanie licznik = licznik -1 pierwszemu wątkowi, a następnie pozwolić na to drugiemu wątkowi. W ten sposób unikniemy kolizji. W języku C mamy gotową sygnalizację i jest ona opisana w bibliotece pthread. Aby z niej skorzystać należy zadeklarować zmienną sygnalizacyjną: i wstawić sygnalizację w miejsce, gdzie program działa nieprawidłowo. Do sygnalizacji wykorzystuje się dwóch funkcji: Wszystko fajnie, ale jak określić wrażliwe miejsce, czyli te miejsce, gdzie program działa nie po naszej myśli? Zasada jest prosta. Wrażliwe miejsce to takie, gdzie zmienna globalna może być modyfikowana przez kilka wątków jednocześnie. Korzystając z tej definicji możemy wstawić sygnalizację w różne miejsca w tej samej funkcji, po prostu program będzie inaczej działał (czasem wolniej, czasem szybciej). Ja zaprezentuje wam sygnalizację w 2 różnych miejscach (prawidłowych miejscach). Wstawiając tak sygnalizację, funkcja podczas pracy wykona całą pętle for(), a dopiero po wykonaniu tej pętli da dostęp innemu wątkowi do tej zmiennej. Oczywiście program będzie działał prawidłowo. Innym rozwiązaniem jest wstawienie sygnalizacji w samej pętli for():
W tym przypadku program będzie także działał prawidłowo, po prostu będzie więcej sygnalizacji i program najprawdopodobniej będzie działał dłużej niż ten pierwszy, ponieważ nie jest dobrym zwyczajem wstawiać 2 mln sygnalizacji w 1 programie. Lepiej dać procesorowi wykonać pętlę 1mln razy i oddać zmienną drugiemu wątkowi, niż co jedno działanie oddawać zmienną drugiemu wątkowi. Oczywiście nie zmienia to faktu, że oba programy będą działały prawidłowo. Działający program zamieszczam poniżej.
|