1 / 60

Wątki i ich synchronizacja

Wątki i ich synchronizacja. Jakub Szatkowski Programowanie w środowisku Windows 03.12.2010. Plan prezentacji. Pojęcia: proces, wątek Po co nam wątki? Tworzenie wątków. Kontekst wątku, priorytety wątków Problemy dotyczące synchronizacji wątków Metody synchronizacji wątków

shyla
Télécharger la présentation

Wątki i ich synchronizacja

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Wątki i ich synchronizacja Jakub Szatkowski Programowanie w środowisku Windows 03.12.2010

  2. Plan prezentacji • Pojęcia: proces, wątek • Po co nam wątki? Tworzenie wątków. • Kontekst wątku, priorytety wątków • Problemy dotyczące synchronizacji wątków • Metody synchronizacji wątków • Krótkie omówienie bibliotek

  3. Proces • Procesem można nazwać wykonujący się program. Do wykonania swojej pracy proces potrzebuje na ogół pewnych zasobów takich jak czas procesora, pamięć operacyjna czy urządzenie wejścia-wyjścia. Zasoby te są przydzielane procesowi w chwili jego powstania lub podczas późniejszego działania.

  4. Proces i wątek • W celu wykonania programu system operacyjny przydziela procesowi zasoby ale także może być konieczne współbieżne wykonywanie pewnych fragmentów programu działających na tych samych danych. Aby to zrealizować program może zażądać utworzenia określonej liczby wątków wykonujących wskazane części programu. Wątki te współdzielą wszystkie zasoby procesu oprócz czasu procesora, który jest przydzielany każdemu wątkowi osobno. • Przykład: WczytajDane(&a1); WczytajDane(&a1); PrzetwórzDane(&a1); PrzetwórzDane(&a1); i WczytajDane(&a2); WypiszDane(a1); WypiszDane(&a1); i PrzetworzDane(&a2);

  5. Wątek • Wątek, jest 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ątek składa się z dwóch elementów: obiektu jądra oraz stosu wątku. Obiekt-wątek jest używany przez system operacyjny do zarządzania wątkiem. Ponadto, system przechowuje w tym obiekcie informacje o wątku. Na stosie wątku natomiast, trzymane są wszystkie parametry funkcji i zmienne lokalne potrzebne do wykonania kodu wątku. Należy wiedzieć, że sam proces w zasadzie niczego nie wykonuje. Jest tylko "pojemnikiem" zawierającym wątki. Proces musi mieć przynajmniej jeden wątek, tzw. wątek główny, ale możliwe jest utworzenie większej liczby wątków w kontekście jednego procesu. Co za chwilę uczynimy.

  6. Wątek • Jednostka dla której system przydziela czas procesora • Każdy proces ma co najmniej jeden wątek • Proces nie wykonuje kodu, proces jest obiektem dostarczającym wątkowi przestrzeni adresowej i odpowiednich zasobów • Kod zawarty w procesie jest wykonywany przez wątek • Pierwszy wątek procesu tworzony jest automatycznie przez system operacyjny, każdy następny musi być utworzony przez programistę • Wszystkie wątki tego samego procesu dzielą przestrzeń adresową i mają dostęp do tych samych zmiennych globalnych i zasobów systemowych.

  7. Po co nam wątki? • Wiemy, że wieloprogramowość sprawia, że efektywniej wykorzystujemy procesor. Tak samo wątki w danym procesie sprawiają, że ten proces wykorzystuje efektywniej przydzielony mu czas procesora. • Jako przykład posłużmy się żmudnym rekurencyjnym wyliczaniem 3 wartości wyrazów ciągu Fibonacciego i 2 sortowań bąbelkowych tablic liczb całkowitych, wszystkie wyniki zapisywane są do plików i porównamy czas działania programu gdzie mamy 5 wątków i każdy z nich liczy odpowiedni wyraz ciągu lub tworzy i sortuje tablicę, a następnie zapisuje do pliku swój wynik i programu gdzie 1 wątek po kolei wykonuje te same instrukcje.

  8. Funkcje tworząca wątek • Aby stworzyć wątek w windows używamy funkcji: HANDLE CreateThread( PSECURITY_ATTRIBUTES psa, DWORD cbStack, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadId );

  9. Argumenty funkcji CreateThread • PSECURITY_ATTRIBUTES psa Wskaźnik do struktury SECRUITY_ATTRIBUTES, która wygląda tak: typedef struct _SECURITY_ATTRIBUTES    {          DWORD nLength;           LPVOID lpSecurityDescriptor;          BOOL bInheritHandle;    } SECURITY_ATTRIBUTES Każda funkcja tworząca dowolny obiekt jądra pobiera wskaźnik do tej struktury. Parametr nLength określa rozmiar struktury, parametr lpSecurityDescriptor to adres zainicjalizowanego deskryptora bezpieczeństwa. Parametr bInheritHandle decyduje on o tym, czy tworzony obiekt jądra jest dziedziczny. My nie będziemy dziedziczyć wątków i będziemy chcieli ustawić standardowe atrybuty bezpieczeństwa, dlatego będziemy przekazywać NULL.

  10. Argumenty funkcji CreateThread • DWORD cbStack Określa ilość przestrzeni adresowej jaką wątek może przeznaczyć na swój stos (każdy wątek go posiada). Podanie wartości 0 powoduje przydzielenie stosowi wątku całej pamięć zarezerwowana dla tego wątku. Dla przypomnienia: DWORD ≈ Unsigned Int

  11. Argumenty funkcji CreateThread • PTHREAD_START_ROUTINE pfnStartAddr Określa adres funkcji od której ma się zacząć wykonywanie wątku. Przykładowa definicja takiej funkcji wygląda tak: DWORD WINAPI FunkcjaWatku(PVOID pvParam){ cout<<"Przekazana wartosc:” <<*(int*)pvParam; return 0; } • PVOID pvParam Dowolny parametr przekazywany do funkcji wątkowej.

  12. Argumenty funkcji CreateThread • DWORD fdwCreate Określa dodatkowe flagi. Podanie 0 powoduje, że wątek jest od razu wprowadzony do kolejki wykonania, podanie w tym miejscu flagi: CREATE_SUSPENDED spowoduje, że wątek po utworzeniu będzie zawieszony i będzie oczekiwał na wznowienie go funkcją ResumeThread( HANDLE hThread ) gdzie jako argument podajemy uchwyt do wątku. W każdej chwili możemy wątek zawiesić funkcją SuspendThread(HANDLE hThread);

  13. Argumenty funkcji CreateThread • PDWORD pdwThreadId Ostatni parametr funkcji CreateThread musi przekazywać adres zmiennej typu DWORD, która będzie przechowywać identyfikator (ID) utworzonego wątku. Np. deklarujemy DWORD watek1; i wywołujemy CreateThread(…,&watek1);

  14. Zakończenie wątku • Istnieją różne sposoby zakończenia wątku w Windows: • VOID ExitThread( DWORD dwExitCode ); ta funkcja zabija wątek, który ją wywołał, jej parametr określa kod wyjścia wątku. • BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode ); Pozwala zabić dowolny wątek do którego podamy uchwyt. • Wątek zginie też wtedy gdy zabijemy proces, w przestrzeni którego owy wątek działał. • Gdy funkcja wątkowa zakończy swoje działanie to nie musimy wywoływać żadnej funkcji zamykania, watek zakończy się samoistnie.

  15. Tworzenie wątku, co się dzieje? • Kiedy wywołujemy funkcję CreateThread() z odpowiednimi parametrami tworzymy nowy obiekt jądra i system inicjalizuje pewne jego własności takie jak: licznik użyć, licznik zawieszeń, kod wyjścia i stan obiektu. System alokuje pamięć na stos wątku, która pochodzi z przestrzeni adresowej procesu do którego należy ów wątek. Do stosu zapisywany jest adres funkcji przekazywanej w CreateThread(), a także jej parametr. Każdy wątek ma też swój zbiór rejestrów CPU zwany kontekstem. Kontekst przedstawia stan obiektu jądra jakim jest wątek. Zbiór rejestrów przechowywany jest w strukturze CONTEXT. Przechowywane w niej są m.in. rejestr wskaźnika instrukcji i rejestr wskaźnika stosu.

  16. Kontekst wątku • To zbiór rejestrów CPU z których wątek korzysta. Kiedy mija czas wykonywania wątku system przerywa jego działania i bierze z kolejki kolejny wątek. Aby pierwszy wątek mógł dalej kontynuować swoje działanie rejestry CPU muszą mieć taką samą wartość jak w momencie przerwania działania. Te wartości są przechowywane właśnie w kontekście wątku. Następuje przełączanie kontekstów wraz z obsługą kolejnych wątków. Struktura CONTEXT pozwala systemowi pamiętać stan wątku i podjąć działanie od tego momentu, w którym ostatnio wątek je zakończył. Programista jest w stanie w dowolnej chwili odczytać kontekst wątku funkcją GetThreadContext(HANDLE hThread, PCONTEXT pContext);

  17. Kontekst wątku • W przykładowej aplikacji wyciągamy kontekst wątku i sprawdzamy rejestry CPU Eax, Ebx, Ecx i Edx są to rejestry ogólnego przeznaczenia. Są to odpowiednio: • EAX – rejestr akumulacji • EBX – rejestr bazowy • ECX – rejestr licznika • EDX – rejestr danych Windows daje nam możliwość dowolnego ustawienia rejestrów w kontekście dzięki funkcji BOOL SetThreadContext(HADLE hThread, CONST CONTEXT *pContext);

  18. Zatrzymanie wątku Wątek możemy uśpić używając do tego funkcji: VOID Sleep( DWORD dwMilliseconds); Wątek w którym wywołamy tę funkcję zostanie zawieszony na czas określony przez parametr oddając przy tym swój czas korzystania z CPU. Natomiast funkcja: BOOL SwitchToThread(); Sprawia, że jeśli istnieje inny wątek oczekujący jako pierwszy w kolejce na przydział CPU to system przydzieli mu natychmiastowo kwant czasu na wykonanie. Przydatna np. gdy chcemy przydzielić procesor wątkowi o niższym priorytecie

  19. Priorytety wątku • Wątki mogą mieć różne priorytety co ma decydujący wpływ na to, w której kolejności się wykonają. • Każdy wątek ma przypisany bazowy poziom priorytetu będący liczbą z przedziału 0-31 (31–najwyższy priorytet, 0-najniższy). • Algorytm przydzielający CPU wątkom działa tak by najpierw obsłużyć wątki o wyższym priorytecie. • System Windows jest systemem z wywłaszczeniem, a więc jeśli procesor jest przydzielony wątkowi o małym priorytecie, a w kolejce pojawi się wątek o większym priorytecie natychmiastowo zatrzymuje działanie wątku o niższym priorytecie i daje możliwość wykonania się wątku o wyższym priorytecie. • Wątki mogą zmieniać swoje priorytety

  20. Priorytety wątku • Priorytet jest określany na podstawie dwóch wartości: klasy priorytetu procesu w którym wykonuje się wątek i względnego priorytetu wątku w obrębie procesu. • W systemie Windows istnieje 6 klas priorytetów (dotyczą procesów!) o następujących właściwościach: • Czasu rzeczywistego - (wątki wywłaszczają nawet składowe systemu) • Wysoki – np. Eksplorator Windows lub Menadżer Zadań • Powyżej normalnego • Normalny – standardowa klasa w systemie Windows, jeśli tworzymy proces nie określając klasy priorytetu to właśnie ta klasa jest mu przydzielana • Poniżej normalnego • Niski – wątki wykonują się w momentach bezczynności systemu, np. wygaszacz ekranu.

  21. Priorytety wątków • Kolejną rzeczą określającą priorytet wątku jest względny priorytet wątku. Windows ma 7 takich rodzajów priorytetów: • Krytyczny • Najlepszy • Powyżej normalnego • Normalny • Poniżej normalnego • Najgorszy • Niski • Jeżeli chce się ustalić priorytet wątku jako liczbę od 0 do 31 to należy ustalić zarówno klasę priorytetu procesu jak i względny priorytet wątku.

  22. Priorytety wątków

  23. Klasa priorytetu procesu • Aby ustawić klasę priorytetu procesu można wywołać proces funkcją CreateProcess() i parametrze fwdCreate ustawić którąś z poniższych flag: •      REALTIME_PRIORITY_CLASS - czasu rzeczywistego. •      HIGH_PRIORITY_CLASS - wysoki.•      ABOVE_NORMAL_PRIORITY_CLASS - powyżej normalnego.•      NORMAL_PRIORITY_CLASS - normalny.•      BELOW_NORMAL_PRIORITY_CLASS - poniżej normalnego.•      IDLE_PRIORITY_CLASS - niski. Można również użyć funkcji: BOOL SetPriorityClass(HANDLE hProcess,DWORD fdwPriority); Jeśli chcemy sprawdzić jaką nasz proces ma klasę priorytetu możemy użyć funkcji: DWORD GetPriorityClass( HANDLE hProcess); Funkcja zwraca jeden z identyfikatorów podanych powyżej.

  24. Priorytet względny wątku • Priorytet względny ustawiamy wywołując funkcję: BOOL SetThreadPriority(HANDLE hThread, int nPriority); W parametrze nPriority przekazujemy jeden z poniższych identyfikatorów określających priorytet: •      THREAD_PRIORITY_TIME_CRITICAL - krytyczny.•      THREAD_PRIORITY_HIGHEST - najlepszy.•      THREAD_PRIORITY_ABOVE_NORMAL - powyżej normalnego.•      THREAD_PRIORITY_NORMAL - normalny.•      THREAD_PRIORITY_BELOW_NORMAL - poniżej normalnego.•      THREAD_PRIORITY_LOWEST - najgorszy.•      THREAD_PRIORITY_IDLE - niski. Gdy chcemy pobrać względny priorytet wątku należy użyć funkcji GetThreadPriority(handle hThread); i przekazać jej odpowiedni uchwyt do wątku.

  25. Podwyższanie priorytetów • Zdarza się, że system sam podwyższa priorytet danego wątku. Gdy mamy sytuację gdy wątek o małym priorytecie nie może uzyskać dostępu do CPU bo wykonują się wątki o wyższych priorytetach i system taką sytuację wykryje to podwyższa priorytet tego wątku do 15 i pozwala mu się wykonać przez dwa kwanty czasu poczym przywraca mu priorytet do wartości bazowej. • Jeśli chcemy możemy w swoim programie zablokować takie działania systemu używając funkcji: BOOL SetProcessPriorityBoost( HANDLE hProcess, BOOL DisablePriorityBoost);lub wyłączyć taki mechanizm dla danego wątku: BOOL SetThreadPriorityBoost( HANDLE hThread, BOOL DisablePriorityBoost);Sprawdzenie włączenia to oczywiście: BOOL GetProcessPriorityBoost(HANDLE hProcess, PBOOL pDisablePriorityBoost);lub:BOOL GetThreadPriorityBoost(HANDLE hThread, PBOOL pDisablePriorityBoost);

  26. Wątki i priorytety - praktyka • Ustawimy priorytet procesu jakim jest nasz program na powyżej normalnego • Utworzymy dwa wątki, jednego względy priorytet ustawimy na poniżej normalnego a drugi na najlepszy • Pobierzemy od systemu uchwyt do głównego wątku naszego programu i ustawimy jego względny priorytet na najniższy • Każdy wątek będzie 50 razy wyświetlał informacje o swoim numerze i liczniku wyświetleń

  27. Dlaczego synchronizacja? • Jak widzieliśmy w poprzednim programie gdy na chwilę zaczął działać jakiś wątek to inny wątek przerywał jego tekst i pisał swój. By tak nie było potrzebny będzie jakiś mechanizm synchronizacji używany najczęściej przy dostępie wątków do współdzielonych danych lub urządzeń. • Gdyby dwa różne wątki coś drukowały na drukarce i nie byłyby one zsynchronizowane nie otrzymalibyśmy satysfakcjonującego nas wydruku.

  28. Dlaczego synchronizacja? • Wątki współzawodniczą o dostęp do zasobów. Zasoby te najczęściej w danej chwili mogą być wykorzystywane tylko przez jeden wątek (lub przez liczbę wątków mniejszą od chętnych). To dość często spotykana sytuacja w życiu codziennym. Np. poranne korzystanie z łazienki. Przecież najpierw czekamy aż łazienka się zwolni, a potem z niej korzystamy. Gdy jednak dwie osoby naraz chcą wejść do łazienki to albo porównujemy priorytety swoich potrzeb albo ktoś musi zastosować zasadę uprzejmości.

  29. Zasób dzielony i sekcja krytyczna • W teorii programowania współbieżnego (gdzie różne procesy lub wątki korzystają ze wspólnych zasobów) obiekt, z którego może korzystać wiele procesów lub wątków w sposób wyłączny nazywa się zasobem dzielonym (np. łazienka, drukarka), natomiast fragment wątku, w którym korzysta on z zasobu dzielonego nazywa się sekcją krytyczną (np. mycie się, drukowanie). • W danej chwili z obiektu dzielonego może korzystać tylko jeden wątek wykonując sekcję krytyczną uniemożliwia on wykonanie sekcji krytycznych innym wątkom.

  30. Wzajemne wykluczanie • Wzajemne wykluczanie definiuje się tak by zsynchronizować wątki by każdy zajmował się własnymi sprawami i wykonywał sekcję krytyczną w taki sposób aby nie pokrywało się to z wykonaniem sekcji krytycznej innych wątków. • Aby to zrealizować należy do funkcji każdego wątku dodać dodatkowe instrukcję poprzedzające i następujące po sekcji krytycznej. Realizujemy czekanie na wolną łazienkę i zasadę uprzejmości.

  31. Wymagania czasowe • Projektując wzajemne wykluczanie musimy uwzględnić kilka ważnych aspektów: • Żaden wątek nie może wykonywać swej sekcji krytycznej nieskończenie długo, nie może się w niej zapętlić lub zakończyć w wyniku jakiegoś błędu (jeśli ktoś umrze w łazience to będziemy czekać nieskończenie długo jeśli nie sprawdzimy co się stało) • Ważna zasada jest taka by w sekcji krytycznej wątek przebywał jak najkrócej i nie miał możliwości zakończenia się błędem.

  32. Blokada • W pewnych sytuacjach wątek będzie wstrzymywany w oczekiwaniu na sygnał od innego wątku (gdy łazienka się zwolni) • Blokada występuje wtedy gdy zbiór procesów jest wstrzymany w oczekiwaniu na zdarzenie, które może być spowodowane tylko przez jakiś inny proces z tego zbioru. • Jaś jest w łazience i czeka aż zwolni się komputer a Małgosia siedzi przed komputerem w oczekiwaniu na zwolnienie się łazienki

  33. Zagłodzenie • Jeśli sygnał synchronizujący może być odebrany tylko przez jeden wątek czekający to trzeba któryś wybrać • Oznacza to, że wątek o niskim priorytecie może się zagłodzić gdyż ciągle będą wybierane wątki o wyższym priorytecie • W systemie Windows jak już wiemy omijanie zagłodzenia jest realizowane poprzez dynamiczne podwyższanie priorytetów.

  34. Problem producenta i konsumenta • Problem ten polega na zsynchronizowaniu dwóch wątków: producenta, który cyklicznie produkuje jakąś informację i przekazuje ją do konsumpcji i konsumenta, który cyklicznie pobiera tą informację i konsumuje ją. Informacje powinny być konsumowane w kolejności wyprodukowania.

  35. Problem czytelników i pisarzy • Problem polega na zsynchronizowaniu dwóch grup cyklicznych wątków konkurujących o dostęp do jednej czytelni. Wątek czytelnik co jakiś czas odczytuje informacje z czytelni (może to robić z innymi czytelnikami) natomiast wątek pisarz co jakiś czas zapisuje nową informację i wówczas musi przebywać sam w czytelni. • Rozwiązanie z możliwością zagłodzenia pisarzy (pisarz może wejść gdy czytelnia jest pusta) • Rozwiązanie z możliwością zagłodzenia czytelników (jeśli pisarz chce pisać to należy mu to jak najszybciej umożliwić) a więc nowi czytelnicy nie wchodzą a pisarz czeka, aż czytelnicy siedzący w czytelni ją opuszczą

  36. Problem pięciu filozofów • Problem polega na zsynchronizowaniu działań pięciu chińskich filozofów, którzy siedzą przy okrągłym stole i myślą. Jednak od czasu do czasu każdy filozof głodnieje i aby móc dalej myśleć musi się pożywić. Przed każdym filozofem stoi miska z ryżem, a pomiędzy dwoma miskami stoi jedna pałeczka. Podnosząc obie pałeczki filozof uniemożliwia jedzenie sąsiadom. Zakłada się, że jeżeli filozof podniósł pałeczki to w skończonym czasie się naje i odłoży je na miejsce. • Rozwiązanie z możliwością blokady (głodny filozof czeka aż będzie wolna lewa pałeczka i ją podnosi, a następnie czeka, aż będzie wolna prawa pałeczka, podnosi ją i je, co się stanie gdy każdy chwyci lewą pałeczkę? ) • Rozwiązanie z możliwością zagłodzenia (głodny filozof czeka aż obie pałeczki będą wolne, wtedy je podnosi i je, jeden filozof może siedzieć pomiędzy takimi, że zawsze któryś z nich będzie jadł)

  37. Mechanizmy synchronizacji w WinAPI • Zdarzenia • Mutexy • Semafory • Sekcje krytyczne • Zegary oczekujące

  38. Zdarzenia • Możemy sobie zdefiniować własne zdarzenie dzięki funkcji CreateEvent(). Zdarzenie raz zgłoszone istnieje w systemie do momentu odwołania. Każdy oczekujący watek widzi to zdarzenie jako dwustanową flagę (zgłoszone lub odwołane). Zgłaszamy zdarzenie funkcją SetEvent() i wszystkie wątki oczekujące mogą wznowić działanie. Funkcją ResetEvent() odwołujemy zdarzenie. Czekanie realizujemy za pomocą funkcji WaitForSingleObject(); W CreateEvent podajemy flagę ręcznego odwołania: TRUE wymaga abyśmy użyli ResetEvent() natomiast FALSE sprawia, że po przepuszczeniu wątku zdarzenie zostaje automatycznie odwołane. Od tej flagi zależy również działanie funkcji PulseEvent(), jeśli jest TRUE to funkcja PulseEvent przepuszcza wszystkie wątki oczekujące w danej chwili na zdarzenie, a jeśli FALSE to PulseEvent przepuszcza jeden z wątków oczekujących po czym odwołuje zdarzenie.

  39. Zdarzenia • W przykładzie opisującym zdarzenia utworzymy wątek, który będzie czekał na otwarcie pliku i wtedy wykona swoją operację. • Po utworzeniu pliku wątek główny będzie czekał aż wątek do czytania wykona swoją operację. • Warto wspomnieć, że jeśli chcemy zaczekać na kilka zdarzeń, możemy użyć funkcji WaitForMultipleObjects();

  40. Mutexy • Mutexy służą do realizacji wzajemnego wykluczania się. Stan mutexa jest ustawiany na sygnalizowany (kiedy żaden wątek nie sprawuje nad nim kontroli) lub niesygnalizowany (kiedy jakiś wątek sprawuje nad nim kontrolę). Każdy wątek czeka na objęcie mutexa w posiadanie zaś po zakończeniu operacji wymagającej wyłączności wątek uwalnia mutexa.

  41. Mutexy • W celu stworzenia mutexa wywołujemy funkcję CreateMutex(). Wątek, który stworzył mutexa rząda natychmiastowego prawa do własności mutexa. Inne wątki (lub procesy) otwierają mutexa za pomocą funkcji OpenMutex(). Potem czekają na objęcie mutexa w posiadanie. Aby uwolnić mutexa wywołamy funkcję ReleaseMutex(). • Jeśli wątek zakończy się i nie uwolni mutexa to taki mutex uważa się za porzucony. Każdy czekający wątek może objąć takiego mutexa w posiadanie. • Na mutexa czekamy oczywiście funkcją WaitForSingleObject()

  42. Mutexy • W przykładzie pokazującym działanie mutexów pokażemy, jak poszczególne wątki będą inkrementowały bądź dekrementowały zmienną globalną. Sekcją krytyczną będzie operacja inkrementacji lub dekrementacji i wypisywanie stosownej informacji na ekranie. Ujrzymy przy tym, że w czasie gdy jeden wątek wykonuje swoją sekcję krytyczną to inne wątki sekcji krytycznej objętej tym mutexem nie wykonują.

  43. Semafory • Semafory to narzędzie służące do kontroli ilości wątków korzystających z zasobu. Za pomocą semaforu aplikacja może kontrolować np. maksymalną ilość otwartych plików. Semafory są dość podobne do mutexów. Nowy semafor tworzony funkcją CreateSemaphore(). Wątek tworzący semafor ustala wartość wstępną i maksymalną licznika. Inne wątki uzyskują dostęp do semafora za pomocą funkcji OpenSemaphore() i czekają na wejście za pomocą funkcji WaitForSingleObject(). Po zakończeniu sekcji krytycznej uwalnia się semafor za pomocą funkcji ReleaseSemaphore(). Wątki nie wchodzą w posiadanie semaforu tak jak to było z mutexem. Jeśli wątek, który stworzył mutex żąda do niego dostępu to otrzymuje go natychmiast. Z semaforem jest inaczej tzn. wątek, który stworzył semafor czeka w kolejce jak każdy inny wątek.

  44. Zasada działania semafora • Inicjalizacja licznika i jego maksymalna wartość • Kiedy licznik jest większy od 0 pierwszy w kolejce wątek może wejść do swojej sekcji krytycznej • Kiedy wątek wchodzi do swojej sekcji krytycznej zmniejsza o 1 wartość licznika • Jeśli licznik nie jest równy 0 to inny wątek również może go podnieść i korzystać z sekcji krytycznej • Kiedy wątek opuszcza sekcję krytyczną i zwalnia semafor, wartość licznika zostaje podwyższona o 1. Dzięki temu kolejny wątek może wejść do swojej sekcji krytycznej. • Gdy z zasobu może korzystać tylko jeden wątek wtedy tworzymy tzw. semafor binarny, inicjalizujemy licznik na 1, a wartość maksymalną ustawiamy na 1.

  45. Semafory • W funkcji CreateSemaphore podajemy następujące argumenty: • LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, wskaznik do atrybutu ochrony (my podajemy NULL) • LONG lInitialCount, wartość początkowa licznika • LONG lMaximumCount, maksymalny licznik • LPCTSTR lpName, wskaźnik do nazwy semafora • Funkcja zwraca uchwyt do semafora • Gdy wątek kończy działanie sekcji krytycznej wywołuje funkcję ReleaseSemaphore() podając w argumentach: uchwyt do semafora, zwolniony licznik i wskaźnik do poprzedniego licznika, (NULL jeśli nie jest to potrzebne).

  46. Semafor i rozwiązanie problemu pięciu filozofów • Za pomocą semaforów rozwiążemy ten problem korzystając z rozwiązania z możliwością blokady • Paleczka[i] będzie reprezentować semafor pałeczki o numerze i. Podniesienie pałeczki będzie to korzystanie z i-tego semafora, a skończenie jedzenie będzie to jego zwolnienie.

  47. Sekcje krytyczne • Narzędzie systemu Windows tylko do obsługi współbieżności wątków (w odróżnieniu od zdarzeń, mutexów i semaforów). • Umożliwia implementację sekcji krytycznej • Najczęściej stosowana metoda w synchronizacji wątków w Windows

  48. Sekcje krytyczne • Tworzymy zmienną typu CRITICAL_SECTION • W każdej z poniższych funkcji jako argument podajemy adres zmiennej którą utwożylismy • Inicjalizujemy sekcję krytyczną: VOID InitializeCriticalSection(); • Wejście do sekcji krytycznej: VOID EnterCriticalSection(); • Opuszczenie sekcji krytycznej:VOID LeaveCriticalSection(); • Zamknięcie sekcji krytycznej: VOID DeleteCriticalSection(); • W Windows NT gdy chcemy wejść do sekcji krytycznej mamy możliwość użycia funkcji:BOOL TryEnterCriticalSection();

  49. Sekcje krytyczne - przykład • W przykładowej aplikacji znów posłużymy się synchronizacją wyświetlania informacji na ekran. • Utworzymy 2 sekcje krytyczne by pokazać, że mechanizm CRITICAL_SECTION ułatwia tworzenie wielu sekcji krytycznych i łatwiej nad nimi panować bo funkcję wymagają tylko adresu zmiennej. • Pierwsza sekcja krytyczna będzie służyć operacji na globalnej zmiennej a druga będzie służyć wyświetlaniu informacji o stanie tej zmiennej.

  50. Zegary oczekujące • Zegary umożliwiają głównie regularne wywoływanie wątków. • Zegar oczekujący przechodzi w stan sygnalizowany po upływie określonego czasu lub w określonych odstępach czasu z automatycznym powrotem do stanu niesygnalizowanego. • Zegar oczekujący tworzymy funkcją:CreateWaitableTimer(), której argumenty to:wskaźnik na strukturę SA, zmienna BOOL mówiąca o tym czy zegar ustawiamy ręcznie (TRUE) czy automatycznie (FALSE), nazwa zegara. • Funkcja zwraca uchwyt do zegara.

More Related