Systemy rozproszone

30
Systemy rozproszone W. Bartkiewicz Wykład 9. Wprowadzenie do koordynacji programów współbieżnych

description

Uniwersytet Łódzki Katedra Informatyki. W. Bartkiewicz. Systemy rozproszone. Wykład 9. Wprowadzenie do koordynacji programów współbieżnych. Katedra Informatyki. Współbieżność. - PowerPoint PPT Presentation

Transcript of Systemy rozproszone

Page 1: Systemy rozproszone

Systemy rozproszone

W. Bartkiewicz

Wykład 9. Wprowadzenie do koordynacji programów współbieżnych

Page 2: Systemy rozproszone

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.

Page 3: Systemy rozproszone

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.

Page 4: Systemy rozproszone

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.

Page 5: Systemy rozproszone

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).

Page 6: Systemy rozproszone

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):1. Wątek fun1 sprawdza, że enter1 ma wartość true.2. Wątek fun1 zostaje wywłaszczony (lub pechowa

kolejność operacji równoległych).3. 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.

4. Wątek fun2 zostaje wywłaszczony.5. 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ą

Page 7: Systemy rozproszone

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:1. Wątek fun1 ustawia enter2 na false.2. Wątek fun1 zostaje wywłaszczony (lub pechowa

kolejność operacji równoległych).3. Wątek fun2 ustawia enter1 na false.4. Wątek fun2 sprawdza, że enter2 ma wartość false i

jest zablokowany przez pętlę while.5. Wątek fun2 zostaje wywłaszczony.6. Wątek fun1 sprawdza enter2 i też jest zablokowany

przez pętlę while.7. Obydwa wątki są zablokowane i czekają na siebie.

Oooops!! Zakleszczenie

Page 8: Systemy rozproszone

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ć.

Page 9: Systemy rozproszone

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.

Page 10: Systemy rozproszone

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.

Page 11: Systemy rozproszone

Synchronizacja w trybie użytkownikaSekcje krytyczne

• 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.

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);}

Page 12: Systemy rozproszone

• 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.

Synchronizacja w trybie użytkownikaSekcje krytyczne

Page 13: Systemy rozproszone

• 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.

Synchronizacja w trybie użytkownikaSekcje krytyczne

Page 14: Systemy rozproszone

• 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ń.

Synchronizacja w trybie użytkownikaSekcje krytyczne

Page 15: Systemy rozproszone

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.

Page 16: Systemy rozproszone

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.

Page 17: Systemy rozproszone

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.

Page 18: Systemy rozproszone

Synchronizacja – obiekty jądraMuteksy

• 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.

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);}

Page 19: Systemy rozproszone

Synchronizacja – obiekty jądraMuteksy

• 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.

HANDLE CreateMutex( PSECURITY_ATTRIBUTES pSA, // atrybuty bezpieczeństwa (u nas NULL) BOOL fInitialOwner, // czy muteks od razu zajęty PCTSTR pszName // nazwa muteksu);

Page 20: Systemy rozproszone

Synchronizacja – obiekty jądraMuteksy

• 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.

HANDLE CreateMutex( PSECURITY_ATTRIBUTES pSA, // atrybuty bezpieczeństwa (u nas NULL) BOOL fInitialOwner, // czy muteks od razu zajęty PCTSTR pszName // nazwa muteksu);

Page 21: Systemy rozproszone

Synchronizacja – obiekty jądraMuteksy

//Pierwszy programvoid 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 programvoid 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);}

Page 22: Systemy rozproszone

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.

Page 23: Systemy rozproszone

Synchronizacja – obiekty jądraSemafory

• 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.

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 ;}

Page 24: Systemy rozproszone

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

Page 25: Systemy rozproszone

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.

Page 26: Systemy rozproszone

Synchronizacja – obiekty jądraZdarzenia

• 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.

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);

Page 27: Systemy rozproszone

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); ...}

Page 28: Systemy rozproszone

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.

Page 29: Systemy rozproszone

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.

Page 30: Systemy rozproszone

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);...

}