Programowanie współbieżne Wykład 9 Synchronizacja dostępu do współdzielonych zasobów Iwona Kochańska
Sekcja krytyczna Instrukcje wykonywane na zmiennych współdzielonych tworzą sekcję krytyczną. Instrukcje wykonywane przez wątek na zmiennych lokalnych niewpływające na stan globalny noszą nazwę sekcji lokalnej W każdym momencie tylko jeden wątek może znajdować się w sekcji krytycznej. W programie może być wiele niepowiązanych ze sobą sekcji krytycznych Konieczna jest synchronizacja dostępu do sekcji krytycznych, tak aby zapewnić wzajemne wykluczanie wątków / procesów Sekcje krytyczne realizuje się np. z wykorzystaniem muteksów lub semaforów.
Muteksy Mutex (ang. Mutual Exclusion, wzajemne wykluczanie) - element stosowany w programowaniu służący do kontroli dostępu do zasobów niemożliwych do współdzielenia, a więc do realizowania sekcji krytycznych.
Muteksy Implementacja muteksu musi spełniać następujące warunki: Nie może prowadzić do blokady - gdy kilka procesów wyraża chęć wejścia do sekcji krytycznej, jeden z nich musi w końcu wejść. Nie może prowadzić do zagłodzenia - jeśli pewien proces wyraża chęć wejścia do sekcji krytycznej, to musi być w końcu dopuszczony -> UCZCIWOŚĆ! Nie może prowadzić do blokady w przypadku braku współzawodnictwa. Gdy tylko jeden proces wyraża chęć wejścia do sekcji krytycznej, powinien zostać dopuszczony.
Muteksy - klasy Obiekt muteksu jest implementacją blokady w bibliotece standardowej C++11. Klasy: mutex, recursive_mutex, timed_mutex, recursive_timed_mutex Zapewnia implementację operacji wzajemnego wykluczenia (mutual exclusion). Umożliwia ochronę współdzielonych zmiennych przed sytuacją wyścigu (race condition). Wątek pozyskuje muteks (wchodzi do sekcji krytycznej) wywołując na rzecz muteksu metodę lock(). Wątek opuszcza sekcję krytyczną - zwalnia muteks - wywołując metodę unlock().
Muteksy Wątek jest prawidłowo skonstruowany, jeżeli spełnia poniższe warunki: Każda sekcja krytyczna jest skojarzona z niepowtarzalnym obiektem blokady Wątek, próbując wejść do sekcji krytycznej, wywołuje metodę lock() tego obiektu Wątek wychodząc z sekcji krytycznej, wywołuje metodę unlock()
Muteksy przykład 1 http://www.cplusplus.com/reference/mutex/mutex/
Przykład 2 współdzielenie obiektu counter
Przykład 2 współdzielenie obiektu counter
Metody obiektu mutex przy wywołaniu unlock wątek powinien zajmować mutex, ale nie jest to wymagane (wątek może zawołać unlock() nawet jeśli wcześniej nie wołał lock()) jeśli mutex wywoła lock() ponownie, dojdzie do zakleszczenia wątku ze sobą, Metoda try_lock() - próbuje zablokować mutex. W przeciwieństwie do lock(), nie blokuje działania wątku, jeśli mutex jest już zablokowany
Klasa recursive_mutex recursive_mutex mutex może być wielokrotnie zajmowany przez jeden wątek, Wątek zajmuje recursive_mutex od momentu wywołania metody lock lub try_lock Wątek może ponownie zawołać lock lub try_lock Wątek zwalnia recursive_mutex po odpowiedniej liczbie wywołań unclock Jeśli wątek zajmuje recursive_mutex, wszystkie inne wątki mają zablokwany dostęp do zasobu
Klasa timed_mutex timed_mutex udostępnia operacje pozwalające zająć mutex tylko przez określony czas Wątek zajmuje recursive_mutex od momentu wywołania metody lock lub try_lock aż do wywołania unlock Wątek zajmuje recursve_mutex na określony czas za pomocą metod: try_lock_for try_lock_until Jeśli wątek zajmuje timed_mutex, wszystkie inne wątki mają zablokowany dostęp do zasobu
Klasa timed_mutex Metoda try_lock_for Blokuje mutex na okreslony czas do momentu osiagnięcia wartości timeout_duration.zero() przez zmienną timeout_duration, lub do momentu wywołania unlock(). W rzeczywistości funkcja może blokować mutex dłużej niż określa to timeout_duration z powodu opóźnień w obsłudze wątków i dostępu do zasobów Do pomiaru czasu zaleca się uzywanie obiektu steady_clock (zamiast system_clock ) Jeśli metoda wołana jest przez wątek, który już posiada mutex, jej zachowanie jest nieokreślone
Klasa timed_mutex Metoda try_lock_until Blokuje mutex na okreslony czas do momentu określonego przez timeout_time lub do momentu wywołania unlock(). W rzeczywistości funkcja może blokować mutex dłużej niż określa to timeout_time z powodu opóźnień w obsłudze wątków i dostępu do zasobów Jeśli metoda wołana jest przez wątek, który już posiada mutex, jej zachowanie jest nieokreślone
Klasy: lock_guard() i unique_lock() Zwykle nie odwołujemy się bezpośrednio do obiektu klasy std::mutex. Klasy opakowujące (wrapper): std::unique_lock i std::lock_guard robią to w bezpieczny sposób ze względu na obsługę wyjątków Klasy opakowujące to funkcje lub klasy, których zadaniem jest jedynie wywołanie innych funkcji lub metod obiektów, najczęściej związane także z konwersją typów argumentów, z używanych przez aplikację na typy wykorzystywane przez wywoływane funkcje. Jeśli użyjemy lock_guard lub unique_lock, które automatycznie wywołają unlock() w destruktorze, to nie trzeba samodzielnie wołać unlock()
Klasy: lock_guard() i unique_lock() Klasa unique_lock Klasa lock_guard Mogą być używane w odniesieniu do obiektów: mutex, recursive_mutex i timed_mutex Różnica: lock_quard jest bardziej bezpieczną wersją unique_lock. Blokada zakładana jest przez konstruktor i zdejmowana przez destruktor. unique_lock własność mutexu może być zablokowana, odblokowana lub przekazana innemu obiektowi w dowolnym momencie
Przykład 3 timed_mutex i lock_quard
Przykład 4 recursive mutex