300 likes | 449 Views
Uniwersytet Łódzki Katedra Informatyki. W. Bartkiewicz. Systemy rozproszone. Wykład 9. Wprowadzenie do koordynacji programów współbieżnych. Katedra Informatyki. Współbieżność.
E N D
Uniwersytet Łódzki Katedra Informatyki W. Bartkiewicz Systemy rozproszone Wykład 9. Wprowadzenie do koordynacji programów współbieżnych
Katedra Informatyki Współbieżność • Jeśli w systemie współistnieje jednocześnie kilka procesów (wątków wykonania), mówimy, że są one wykonywane współbieżnie. • Sytuacja ta dotyczy nie tylko jednoczesnego (równoległego) wykonywania procesów (wątków) na różnych komputerach lub procesorach, ale również naprzemiennego przeplatania porcji poszczególnych procesów (wątków) na jednym procesorze. • Przetwarzanie współbieżne umożliwia zwiększenie wydajności systemu, poprzez przyśpieszenie (równoległość) wykonywania operacji oraz daje możliwość jednoczesnej obsługi wielu użytkowników. • Współbieżność działania powstaje w systemie rozproszonym jako naturalny wynik osobnych działań użytkowników, niezależności zasobów i procesów usługowych. Współbieżny dostęp do zasobów wspólnie wykorzystywanych może jednak powodować konflikty i musi być synchronizowany.
Katedra Informatyki SynchronizacjaWzajemne wykluczanie • Sytuację gdy dla współbieżnie działających elementów aplikacji (wątków lub procesów) pewna operacja może być w danym momencie czasu wykonywana jedynie przez jeden z nich, nazywamy wzajemnym wykluczaniem. • W przypadku gdy jakiś element wykonuje operację podlegającą wzajemnemu wykluczaniu (tzw. sekcję krytyczną), inne elementy próbujące w tym samym czasie wykonywać tę operację muszą zostać zablokowane, aż do jej zakończenia przez pierwszy z nich. • Wzajemne wykluczanie zawiesza więc zasadę współbieżnego wykonywania elementów aplikacji rozproszonej, synchronizując ich dostęp do pewnych operacji. • Większość zagadnień synchronizacji w programowaniu współbieżnym stanowi wariant lub kombinację złożoną z kilku problemów wzajemnego wykluczania.
Katedra Informatyki Przeplot • Podstawowym założeniem programowania współbieżnego jest brak zależności czasowych między wykonywanymi instrukcjami różnych wątków (procesów): • Moment wywłaszczenia wątku (procesu) ma w dużej mierze charakter stochastyczny, zależny od konkretnego uruchomienia programu. • Szybkość wykonania poszczególnych procesów (wątków) uruchamianych równolegle (np. na różnych maszynach) może się zmieniać (np. w zależności od parametrów wykorzystywanego sprzętu, obciążenia sieci itp.) • Analizując program współbieżny nie możemy brać pod uwagę żadnego konkretnego przeplotu instrukcji różnych wątków (procesów). • Program musi być badany pod kątem dowolnego przeplotu instrukcji współbieżnych (wątków) procesów. • Na przykład dla wykazania nieprawidłowego działania aplikacji współbieżnej wystarczy wskazać jeden scenariusz przeplotu, który powoduje błąd.
Katedra Informatyki Synchronizacja • Dowolny przeplot operacji wszystkich synchronizowanych elementów nie może doprowadzić do złamania zasady wzajemnego wykluczania i równoczesnego wejścia kilku z nich do sekcji krytycznej. • Mechanizmy synchronizacji muszą więc zapewniać jednoczesność operacji sprawdzania możliwości wejścia elementu do strefy krytycznej i zablokowania tej możliwości innym elementom. • Obok własności bezpieczeństwa programy współbieżne muszą wykazać się respektowaniem własności żywotności (ang. liveness). • Sytuację gdy synchronizowane elementy zablokują sobie wzajemnie dostęp do strefy krytycznej i żaden z nich nie może wykonać operacji w nich zawartej nazywamy zakleszczeniem lub blokadą (ang. deadlock). • Sytuację, gdy na skutek błędnej synchronizacji pewne z elementów nie otrzymują dostępu do strefy krytycznej nazywamy wykluczeniem (ang. lockout) lub zagłodzeniem (ang. starvation).
Katedra Informatyki Wzajemne wykluczanie – kłopoty (1) bool enter1 = true, enter2 = true; void fun1(void*) { while (1) { while ( !enter1 ) ; enter2 = false; //strefa krytyczna enter2 = true; //jakies operacje } } void fun2(void*) { while (1) { while ( !enter2 ) ; enter1 = false; //strefa krytyczna enter1 = true; //jakies operacje } } void main() { _beginthread(fun1, 0, NULL); _beginthread(fun2, 0, NULL); } • Przykra sytuacja (ale możliwa): • Wątek fun1 sprawdza, że enter1 ma wartość true. • Wątek fun1 zostaje wywłaszczony (lub pechowa kolejność operacji równoległych). • Wątek fun2 sprawdza, że enter2 ma wartość true (bo fun1 nie zdążył zmienić na false) – zmienia na enter1 na false i wchodzi do sekcji krytycznej. • Wątek fun2 zostaje wywłaszczony. • Wątek fun1 nie sprawdza już enter1 (bo zrobił to wcześniej), tylko przypisuje false enter2 i rozpoczyna wykonywanie sekcji krytycznej. • Oooops!! Obydwa wątki wykonują sekcję krytyczną
Katedra Informatyki Wzajemne wykluczanie – kłopoty (2) bool enter1 = true, enter2 = true; void fun1(void*) { while (1) { enter2 = false; while ( !enter1 ) ; //strefa krytyczna enter2 = true; //jakies operacje } } void fun2(void*) { while (1) { enter1 = false; while ( !enter2 ) ; //strefa krytyczna enter1 = true; //jakies operacje } } void main() { _beginthread(fun1, 0, NULL); _beginthread(fun2, 0, NULL); } • Nadal żle: • Wątek fun1 ustawia enter2 na false. • Wątek fun1 zostaje wywłaszczony (lub pechowa kolejność operacji równoległych). • Wątek fun2 ustawia enter1 na false. • Wątek fun2 sprawdza, że enter2 ma wartość false i jest zablokowany przez pętlę while. • Wątek fun2 zostaje wywłaszczony. • Wątek fun1 sprawdza enter2 i też jest zablokowany przez pętlę while. • Obydwa wątki są zablokowane i czekają na siebie. • Oooops!! Zakleszczenie
Katedra Informatyki Synchronizacja System ze wspólną pamięcią • Systemy operacyjne w ramach interfejsów programistycznych oferują (działające na różnych zasadach) specjalne mechanizmy synchronizacyjne, zapewniające jednoczesność testowania warunków wejścia do strefy krytycznej i zablokowania tego dostępu innym współbieżnie działającym wątkom lub procesom. • Przez jednoczesność operacji rozumiemy jej atomowy charakter, tzn. wątek lub proces wykonuje tę operację jako niepodzielną całość. Nie może być w trakcie jej wykonywania wywłaszczony, ani jej instrukcje w żaden sposób nie mogą się przeplatać.
Katedra Informatyki Synchronizacja • Dwa podstawowe podejścia do blokowania współbieżnych elementów aplikacji: • Zablokowane elementy pozostają cały czas aktywne, nieustannie sprawdzając (w pętli) możliwość wejścia do strefy krytycznej – tzw. blokada wirująca (spinning blockade). Rozwiązanie to stosowane jest znacznie rzadziej, ponieważ obciąża procesor. Daje jednak większe możliwości uniknięcia zakleszczeń. • Mechanizmy synchronizacji zawieszają (i kolejkują) zablokowane elementy, aby uniknąć obciążania procesora nieustannym sprawdzaniem przez nie warunków wejścia do strefy krytycznej. Z chwilą kiedy dostęp ten jest możliwy jeden z oczekujących elementów zostaje odblokowany przez mechanizmy synchronizacyjne. • W praktyce stosowane jest zazwyczaj podejście drugie, lub kombinacja zawieszania elementu aplikacji na pewien czas w powiązaniu z blokadą wirującą albo odmierzaniem czasu trwania blokady.
Katedra Informatyki SynchronizacjaMS Windows • Współbieżne procesy i wątki w Windows mogą być synchronizowane: • W trybie użytkownika (dotyczy tylko wątków jednego procesu). • Za pomocą obiektów jądra systemu operacyjnego.
Katedra Informatyki Synchronizacja w trybie użytkownikaSekcje krytyczne CRITICAL_SECTION g_cs; void ThreadFunc1(void*) { EnterCriticalSection(&g_cs); // operacje sekcji krytycznej LeaveCriticalSection(&g_cs); return(0); } void ThreadFunc2(void*) { EnterCriticalSection(&g_cs); // operacje sekcji krytycznej LeaveCriticalSection(&g_cs); return(0); } void main() { InitializeCriticalSection(&g_cs); _beginthread(fun1, 0, NULL); _beginthread(fun2, 0, NULL); } • Do wzajemnego wykluczania wątków tego samego procesu, korzystamy zazwyczaj ze specjalnego typu danych, sekcji krytycznej CRITICAL_SECTION. • Realizacja wzajemnego wykluczania: • Deklarujemy (zazwyczaj jako zmienną globalną dostępną dla wszystkich wątków) zmienną typu CRITICAL_SECTION. • Zmienne ta musi zostać zainicjowana w programie dokładnie raz i przed próbą wejścia do strefy krytycznej przez którykolwiek z wątków, przy pomocy funkcji InitializeCriticalSection. • Wątek wchodząc do strefy krytycznej wywołuje funkcję EnterCriticalSection, wychodząc LeaveCriticalSection.
Katedra Informatyki Synchronizacja w trybie użytkownikaSekcje krytyczne • EnterCriticalSection: • Jeśli żaden wątek nie korzysta z sekcji krytycznej, funkcja ta zaznacza w polach struktury, że wywołujący wątek uzyskał dostęp i wraca, umożliwiając wątkowi kontynuowanie działania i wykonanie strefy krytycznej. • Jeśli z sekcji krytycznej korzysta wątek wywołujący, to funkcja zwiększa licznik dostępów i wraca, umożliwiając wątkowi kontynuowanie działania. Sytuacja ta może mieć miejsce jedynie wtedy gdy wątek dwukrotnie wywoła EnterCriticalSection bez wywołania LeaveCriticalSection. • Jeśli z sekcji krytycznej korzysta inny wątek, wątek wywołujący jest zawieszany w kolejce wątków oczekujących.
Katedra Informatyki Synchronizacja w trybie użytkownikaSekcje krytyczne • LeaveCriticalSection • Funkcja zmniejsza wywołującemu wątkowi licznik dostępów o 1. Jeśli jego wartość jest nadal większa od 0, funkcja nie robi nic więcej i wraca do miejsca wywołania. Tak więc aby udostępnić sekcję krytyczną wątek musi tyle samo razy wywołać LeaveCriticalSection ile razy wywołał EnterCriticalSection. • Jeśli po zmniejszeniu wartość licznika jest równa 0, funkcja sprawdza czy w kolejce nie czekają jakieś inne wątki. Jeśli tak, to w „uczciwy” sposób wybiera jeden z nich i wznawia jego działanie, umożliwiając wykonanie strefy krytycznej. • Jeśli w kolejce nie czeka żaden wątek, funkcja tak ustawia składowe struktury, aby wskazywały na dostępność zasobu.
Katedra Informatyki Synchronizacja w trybie użytkownikaSekcje krytyczne • Funkcje działające na sekcjach krytycznych mają charakter atomowy. • Sekcje krytyczne są wydajnym narzędziem synchronizującym. • Funkcje realizujące ich operacje działają w trybie użytkownika i wątek nie musi przechodzić w tryb jądra, co kosztuje (w obie strony łącznie) około 1000 cykli procesora. • Nie dotyczy to jednak sytuacji gdy strefa krytyczna jest już zajęta i wątek musi przejść w tryb oczekiwania. Oznacza to, że wątek musi zmienić swój tryb wykonania z trybu użytkownika na tryb jądra. • Sekcje krytyczne mogą być wykorzystywane wyłącznie do synchronizacji wątków tego samego procesu. • Dla sekcji krytycznych nie można określić limitu czasu oczekiwania na wejście do strefy krytycznej, co może prowadzić do zakleszczeń.
Katedra Informatyki Synchronizacja – obiekty jądra • Alternatywą jest wykorzystanie do synchronizacji współbieżnie wykonywanych wątków obiektów jądra systemu MS Windows. • Obiekty jądra są bardziej wszechstronne niż mechanizmy trybu użytkownika. • Mogą służyć do synchronizacji wątków różnych procesów. Możliwe jest również określenie limitu czasu oczekiwania. • Ich wadą jest wolniejsze działanie. Każda operacja na obiekcie jądra (również synchronizacyjna) wymaga przejścia w tryb jądra. • Wolniejsze jest również wykonanie kodu funkcji w trybie jądra. • Obiekty jądra, wykorzystywane do synchronizacji, mogą być w stanie sygnalizowanym lub niesygnalizowanym. • Reguły przejścia z jednego stanu w drugi zależą od typu konkretnego obiektu.
Katedra Informatyki Synchronizacja – obiekty jądra • Wątki mogą czekać na niesygnalizowane obiekty, pozostając w stanie oczekiwania do momentu ich zasygnalizowania. • Do przejścia wątku w stan oczekiwania na zasygnalizowanie obiektu służą funkcje WaitForSingleObject oraz WaitForMultipleObjects. • Zmiana stanu sygnalizowany/niesygnalizowany zazwyczaj jest wynikiem wykonania określonej operacji zależnej od konkretnego typu obiektu. • Zarówno funkcje Wait, jak i operacje zmiany stanu obiektu sygnalizowany/niesygnalizowany mają charakter atomowy.
Katedra Informatyki Synchronizacja – obiekty jądraMuteksy • Muteksy są obiektami jądra, które pozwalają zagwarantować wątkowi wyłączność na dostęp do współdzielonego zasobu (Mutex – mutual exclusion, wzajemne wykluczanie). • Muteksy mają identyczną semantykę działania jak sekcje krytyczne, tyle że są obiektami jądra, a nie trybu użytkownika, co umożliwia synchronizację różnych procesów. • Gdy wątek posiada muteks – ma wyłączny dostęp do zasobu chronionego przez ten muteks. • Gdy muteks znajduje się w posiadaniu wątku, nie może go przejąć żaden inny wątek. • Wątek będący właścicielem muteksu może go przejąć wielokrotnie, musi go w takim przypadku odpowiednią liczbę razy zwrócić. • Najważniejsze operacje: • CreateMutex – utworzenie nowego muteksu lub otwarcie istniejącego • OpenMutex – otwarcie istniejącego muteksu • ReleaseMutex – zwolnienie (sygnalizacja) muteksu, udostępniające go innym wątkom.
Katedra Informatyki Synchronizacja – obiekty jądraMuteksy HANDLE hMt; void ThreadFunc1(void*) { WaitForSingleObject(hMt, INFINITE); // operacje sekcji krytycznej ReleaseMutex(hMt); return(0); } void ThreadFunc2(void*) { WaitForSingleObject(hMt, INFINITE); // operacje sekcji krytycznej ReleaseMutex(hMt); return(0); } void main() { hMt = CreateMutex(NULL, FALSE, NULL); _beginthread(fun1, 0, NULL); _beginthread(fun2, 0, NULL); // Po zakończeniu wątków // CloseHandle(hMt); } • Realizacja zadania wzajemnego wykluczania: • Tworzymy muteks przy pomocy funkcji CreateMutex. • Wchodząc do strefy krytycznej próbujemy objąć muteks w posiadanie np. przy pomocy funkcji WaitForSingleObject. • Kończąc strefę krytyczną zwalniamy muteks (sygnalizujemy jego obiekt) przy pomocy funkcji ReleaseMutex. • Proces uzyskujący dostęp do uchwytu obiektu musi go zamknąć przy pomocy funkcji CloseHandle. • Operacje Wait oraz ReleaseMutex charakter atomowy.
Katedra Informatyki Synchronizacja – obiekty jądraMuteksy HANDLE CreateMutex( PSECURITY_ATTRIBUTES pSA, // atrybuty bezpieczeństwa (u nas NULL) BOOL fInitialOwner, // czy muteks od razu zajęty PCTSTR pszName // nazwa muteksu ); • Pierwszy parametr pSA to wskaźnik do struktury atrybutów bezpieczeństwa dla tworzonego obiektu. • Opisuje ona kto utworzył obiekt, kto może go używać, a kto nie. • Zagadnienie jej definiowania dotyczy kwestii programowania sieciowego, a nie współbieżnego i jest poza zakresem tematycznym zajęć. • Użycie wartości NULL udostępnia muteks procesom tego samego użytkownika, który go utworzył. • Parametr fInitialOwner określa, czy muteks ma od razu po utworzeniu zostać zajęty. Wartość FALSE oznacza, że nie i jest zasygnalizowany.
Katedra Informatyki Synchronizacja – obiekty jądraMuteksy HANDLE CreateMutex( PSECURITY_ATTRIBUTES pSA, // atrybuty bezpieczeństwa (u nas NULL) BOOL fInitialOwner, // czy muteks od razu zajęty PCTSTR pszName // nazwa muteksu ); • Parametr pszName pozwala na określenie nazwy muteksu. • Jeśli pszName ma wartość NULL to muteks jest nienazwany, i jego uchwyt musi być jakoś przekazany innym synchronizowanym wątkom. Jest to łatwe dla wątków jednego procesu (używamy zmiennej globalnej). Dla wątków różnych procesów jest to (nieco) bardziej kłopotliwe, dlatego w przypadku synchronizacji procesów standardem jest nazywanie muteksów. • Jeśli muteks ma podaną nazwę, to stanowi ona jego identyfikator w systemie. Każde kolejne wywołanie (również w innych procesach) funkcji CreateMutex lub OpenMutex (rzadziej stosowana) dla obiektu o tej samej nazwie będzie udostępniało uchwyt tego samego, utworzonego już wcześniej muteksu. • Uwaga: nazwa muteksu musi być więc jednoznaczna w systemie, tzn. w danym momencie nie mogą istnieć dwa różne muteksy o tej samej nazwie.
Katedra Informatyki Synchronizacja – obiekty jądraMuteksy //Pierwszy program void main() { HANDLE hMt = CreateMutex(NULL, FALSE, ”SuperHiperMuteks”); // instrukcje pierwszego programu // gdzieś potrzeba synchronizacji z drugim WaitForSingleObject(hMt, INFINITE); // operacje sekcji krytycznej ReleaseMutex(hMt); // wiele kolejnych instrukcji pierwszego programu CloseHandle(hMt); } //Drugi program void main() { HANDLE hMt = CreateMutex(NULL, FALSE, ”SuperHiperMuteks”); //instrukcje drugiego programu // gdzieś potrzeba synchronizacji z pierwszym WaitForSingleObject(hMt, INFINITE); // operacje sekcji krytycznej ReleaseMutex(hMt); // wiele kolejnych instrukcji drugiego programu CloseHandle(hMt); }
Katedra Informatyki Synchronizacja – obiekty jądraSemafory • Semafor rozszerza koncepcję muteksu na wielokrotny lecz limitowany dostęp do zasobów. • Tworząc semafor podaje się maksymalną oraz bieżącą wartość licznika dostępów do zasobów (strefy krytycznej). • Za każdym odwołaniem do semafora, wartość licznika zmniejszana jest o jeden. Gdy licznik osiąga wartość zero, semafor przestaje być sygnalizowany. • System nie dopuszcza ujemnej wartości licznika zasobów, ani wartości większej od maksymalnej. • Zwolnienie semafora powoduje zwiększenie licznika zasobów. Jeśli jego wartość jest większa od zera, staje się on sygnalizowany. • Podstawowe funkcje: • CreateSemaphore – Utworzenie semafora lub otwarcie istniejącego. • OpenSemaphore – Otwarcie istniejącego semafora. • ReleaseSemaphore – Zwolnienie semafora, zwiększenie o określoną wartość licznika związanego z danym semaforem.
Katedra Informatyki Synchronizacja – obiekty jądraSemafory int main(){ HANDLE hSemaphore; hSemaphore=CreateSemaphore (NULL,3 ,3 ,"TestSemafor"); WaitForSingleObject (hSemaphore, INFINITE); printf("I’m working..." ) ; Sleep(5000); ReleaseSemaphore (hSemaphore , 1 ,NULL); CloseHandle(hSemaphore); return 0 ; } • Realizacja zadania wzajemnego wykluczania: • Tworzymy semafor przy pomocy funkcji CreateSemaphore. • Wchodząc do strefy krytycznej zmniejszamy licznik zasobów semafora np. przy pomocy funkcji WaitForSingleObject. • Kończąc strefę krytyczną zwiększamy licznik semafora przy pomocy funkcji ReleaseSemaphore. • Proces uzyskujący dostęp do uchwytu obiektu musi go zamknąć przy pomocy funkcji CloseHandle. • Operacje WaitForSingleObject oraz ReleaseSemaphore mają charakter atomowy.
Katedra Informatyki Synchronizacja – obiekty jądraSemafory HANDLE CreateSemaphore( PSECURITY_ATTRIBUTE pSA, // atrybuty bezpieczeństwa (u nas NULL) LONG lInitialCount, // początkowa wartość licznika LONG lMaximumCount, // maksymalna wartość licznika PCTSTR pszName // nazwa semafora ); BOOL ReleaseSemaphore( HANDLE hsem, // uchwyt obiektu semafora LONG lReleaseCount, // o ile zwiększyć licznik zasobu PLONG plPreviousCount // wskaźnik do zmiennej całkowitej, w której // funkcja zwróci poprzednią wartość licznika ); // może być (i zazwyczaj jest) NULL
Katedra Informatyki Synchronizacja – obiekty jądraZdarzenia • Zdarzenia są najprostszymi obiektami jądra wykorzystywanymi do synchronizacji. • Zazwyczaj wykorzystywane są do zasygnalizowania zakończenia jakiejś operacji. • Istnieją dwa rodzaje obiektów zdarzeń: resetowane automatycznie i ręcznie. • Z chwilą zasygnalizowania zdarzenia resetowanego ręcznie wszystkie wątki oczekujące na to zdarzenie wznawiają swoje działanie. Aby stało się ono ponownie niesygnalizowane należy wywołać odpowiednią funkcję. • Gdy zostanie zasygnalizowane zdarzenie resetowane automatycznie, tylko jeden z oczekujących wątków wznawia swoje działanie, a obiekt zdarzenia staje się ponownie niesygnalizowany.
Katedra Informatyki Synchronizacja – obiekty jądraZdarzenia HANDLE CreateEvent( PSECURITY_ATTRIBUTES pSA, // atrybuty bezpieczeństwa (u nas NULL) BOOL fManualReset, // TRUE – resetowane ręcznie, FALSE - automatycznie BOOL fInitialState, // TRUE – zasygnalizowany, FALSE - niesygnalizowany PCTSTR pszName // nazwa zdarzenia ); • Podstawowe funkcje: • CreateEvent – Utworzenie zdarzenia lub otwarcie istniejącego. • OpenEvent – Otwarcie istniejącego zdarzenia. • SetEvent – Sygnalizacja zdarzenia. • ResetEvent – Zerowanie zdarzenia – ustawienie ponownie w stanie niesygnalizowanym.
Katedra Informatyki HANDLE g_hEvent; //uchwyt do zdarzenia DWORD WINAPI SpellCheck (PVOID pvParam) { // Czekaj na wczytanie danych do pamięci WaitForSingleObject(g_hEvent, INFINITE); // OK. Dane w pamięci - przetwarzaj ... return(0); } DWORD WINAPI GrammarCheck (PVOID pvParam) { // Czekaj na wczytanie danych do pamięci WaitForSingleObject(g_hEvent, INFINITE); // OK. Dane w pamięci - przetwarzaj ... return(0); } int WINAPI WinMain(...) { g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); HANDLE hThread[2]; hThread[0] = _beginthreadex(NULL, 0, SpellCheck, NULL, 0, NULL); hThread[1] = _beginthreadex(NULL, 0, GrammarCheck, NULL, 0, NULL); OpenFileAndReadContentsIntoMemory(...); // Wczytanie SetEvent(g_hEvent);// OK. dane wczytane, no to wio WaitForMultipleObjects(2, hThread, TRUE, INFINITE); //poczekaj na wątki // Wątki skończyły, przetwarzaj wyniki ich pracy CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(g_hEvent); ... }
Katedra Informatyki Czytanie – pisanieSformułowanie problemu • Kolejny standardowy problem programowania współbieżnego związany jest z sytuacją, gdy w systemie mamy szereg wątków (procesów), odczytujących pewne dane oraz szereg wątków (procesów), które te dane zapisują. • Wątki (procesy) zapisujące będziemy dalej nazywać czasami pisarzami, a wątki (procesy) odczytujące – czytelnikami. • Zauważmy, że wiele wątków (procesów) może odczytywać dane jednocześnie. Jednak jeśli któryś chce dane zmodyfikować, to rozsądnie jest na czas zapisu zablokować do nich dostęp dla pozostałych wątków (procesów). • Zapobiegnie to odczytaniu niespójnych informacji (na przykład danych częściowo tylko zmodyfikowanych). • Również jest to niezbędne dla wyeliminowania niespójnych modyfikacji przez kilka wątków (procesów) piszących.
Katedra Informatyki Czytanie – pisanieSformułowanie problemu • Ogólny schemat synchronizacji działania współbieżnych wątków (procesów) możemy więc przedstawić następująco: • Gdy jeden wątek (proces) zapisuje dane, żaden inny nie może tego robić równocześnie. • Gdy jeden wątek (proces) zapisuje dane, żaden inny nie może ich w tym czasie czytać. • Gdy jeden wątek (proces) czyta dane, żaden inny nie może ich w tym czasie zapisywać. • Gdy jeden wątek (proces) czyta dane, inne mogą to robić równocześnie. • Jak łatwo zauważyć problem współdzielonych blokad czytanie – pisanie pełni fundamentalną rolę, przy zarządzaniu współbieżnymi dostępami do danych. • Podstawa synchronizacji w serwerach baz danych.
Katedra Informatyki Czytanie – pisanie Zarys rozwiązania CRITICAL_SECTION mtx; HANDLE stop; int cz = 0; void czytelnik(void*) { while(1) { EnterCriticalSection(&mtx); if (++cz == 1 ) WaitForSingleObject( stop,INFINITE); LeaveCriticalSection(&mtx); czytajdane(); EnterCriticalSection(&mtx); if (--cz == 0 ) ReleaseMutex(stop); LeaveCriticalSection(&mtx); } } void pisarz(void*) { while(1) { WaitForSingleObject( stop,INFINITE); piszdane(); ReleaseMutex(stop); } } void main() { stop = CreateMutex( NULL, FALSE, NULL); InitializeCriticalSection(&mtx); _beginthread(czytelnik, 0, NULL); _beginthread(pisarz, 0, NULL); ... }