Transcript wyklad13

Muteksy
Muteksy (mutex – MUTual EXclusion) są prostymi obiektami synchronizacyjnymi pełniącymi rolę
semaforów binarnych dla wątków (chroniącymi sekcje krytyczne programów). W odróżnieniu od
semaforów w pakiecie IPC występują jedynie pojedynczo (nie tworzą tablic, na których można
wykonywać niepodzielne operacje). Interpretacja ich wartości jest odwrotna: 0 – muteks otwarty,
wartość dodatnia – muteks zamknięty.
W systemach linuksowych występują trzy rodzaje muteksów: szybkie (fast), sprawdzające (error
checking) i rekurencyjne (recursive). Domyślne atrybuty muteksu (ustawiane przez stałą
PTHREAD_MUTEX_INITIALIZER) to (szybki, otwarty).
Muteks szybki jest najprostszy, gdyż pamięta tylko swój stan (otwarty / zamknięty), a nie pamięta, kto
(który wątek) go zamknął. Z tego powodu może stać się przyczyną blokady wątku, który będzie
próbował zamknąć go po raz drugi. Teoretycznie uwolnić wątek z takiej blokady może inny wątek,
wywołując funkcję otwierającą muteks (ale wystąpienie takiej sytuacji świadczy prawdopodobnie
o błędach logicznych w programie).
Muteks sprawdzający jest semaforem binarnym pamiętającym, kto go zamknął (wątek ten staje się
właścicielem muteksu na czas jego zamknięcia). Może w związku z tym zapobiegać błędom:
- nie pozwala drugi raz zamknąć się temu samemu wątkowi (właścicielowi);
- nie pozwala otworzyć się wątkowi niebędącemu jego właścicielem;
- próba otwarcia, jeśli już był otwarty, powoduje sygnalizację błędu.
Muteks rekurencyjny jest „zamkiem zamykanym na wiele spustów” – posiada licznik operacji
zamknięcia (przez właściciela) i aby go otworzyć, potrzeba tyle samo operacji otwarcia (przez
właściciela).
Uwaga
Maksymalna liczba zamknięć muteksu rekurencyjnego jest zależna od implementacji. Dokumentacja
w systemie Linux ani jej nie specyfikuje, ani nie podaje, jaki może być skutek próby przekroczenia.
int pthread_mutex_init (pthread_mutex_t *muteks, const pthread_mutexattr_t *atrybuty);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu (jest to możliwe tylko wtedy, gdy muteks jest tworzony
dynamicznie, a nie statycznie przez kompilator)
muteks – zwracany wskaźnik na zainicjowany muteks
atrybuty – wskaźnik na obiekt atrybutów (mający zainicjować muteks)
Działanie: tworzy muteks o danych atrybutach.
int pthread_mutex_lock (pthread_mutex_t *muteks);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu
muteks – wskaźnik na (zainicjowany) muteks
Działanie: zamyka dany muteks, jeśli był otwarty, i przyporządkowuje mu właściciela, jeśli muteks nie
jest muteksem szybkim. Jeśli muteks już był zamknięty, a zamknąć go próbował wątek
niebędący jego właścicielem, zawiesza dany wątek aż do otwarcia muteksu przez
właściciela. Jeśli próbował zamknąć go ponownie właściciel:
- blokada wątku w przypadku muteksu szybkiego (chyba, że inny wątek go uwolni);
- błąd w przypadku muteksu sprawdzającego;
- zwiększenie licznika zamknięć w przypadku muteksu rekurencyjnego.
int pthread_mutex_trylock (pthread_mutex_t *muteks);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu
muteks – wskaźnik na muteks
Działanie: takie samo, jak pthread_mutex_lock ( ), ale zamiast zawieszać wątek próbujący zamknąć
zamknięty przez inny wątek (lub ten sam, w przypadku szybkiego) muteks, zwraca błąd.
int pthread_mutex_unlock (pthread_mutex_t muteks);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu
muteks – wskaźnik na muteks
Działanie: otwiera dany muteks. Dokładniej: jeśli pod muteksem czekały jakieś wątki, jeden z nich
zostaje uwolniony i przejmuje muteks na własność zamykając go ponownie (należy
przyjąć, że wybór jest losowy). Jeśli żaden wątek nie czekał, muteks pozostaje otwarty
i bez właściciela.
W przypadku muteksu sprawdzającego otwarcie dochodzi do skutku tylko w przypadku
wątku będącego właścicielem (dla innych wątków zwracany jest błąd).
W przypadku muteksu rekurencyjnego licznik zamknięć zostaje zmniejszony o 1 (jeśli
osiągnął wartość 0 i żaden wątek nie czekał, muteks pozostaje otwarty i bez właściciela).
int pthread_mutex_destroy (pthread_mutex_t muteks);
Zwraca: 0 w przypadku błędu
niezerowy kod w przypadku błędu
muteks – wskaźnik na muteks
Działanie: jeśli muteks był utworzony dynamicznie i jest otwarty, funkcja powinna zlikwidować go
i zwrócić do systemu zajmowane przez niego zasoby. Jeśli dynamiczne tworzenie nie
jest zaimplementowane (muteks jest tworzony statycznie przez kompilator), funkcja
jedynie sprawdza, czy muteks jest otwarty (i zwraca błąd, jeśli nie jest).
Zmienne warunkowe
Zmienne warunkowe (condition variable) są mechanizmami synchronizacji zaprojektowanymi do
współpracy z muteksami w sytuacjach, gdy użycie pojedynczych muteksów nie jest wystarczające.
Ich głównym przeznaczeniem jest blokowanie wątków do czasu, aż pewien warunek logiczny
zostanie spełniony (stąd ich nazwa). Jednym z typowych zastosowań jest rozwiązywanie problemu
producenta i konsumenta. Zmienna warunkowa musi być wykorzystywana łącznie z muteksem –
sama nie ma racji bytu.
Podobnie, jak dla przypadku muteksów, standard POSIX przewiduje możliwość dynamicznego
tworzenia i likwidacji zmiennych warunkowych w programie. Jeśli nie jest to zaimplementowane,
zmienne warunkowe są tworzone statycznie przez kompilator.
Działanie zmiennych warunkowych jest dość skomplikowane i trudne do zrozumienia (jak również
do modelowania przy użyciu diagramów stanów i przejść). Zalecane jest zapoznanie się
z przykładem umieszczonym w opisie (manualu) funkcji obsługujących zmienne warunkowe.
int pthread_cond_init (pthread_cond_t *zmienna, pthread_condattr_t *atrybuty);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu (jest to możliwe tylko wtedy, gdy zmienna jest tworzona
dynamicznie, a nie statycznie przez kompilator)
zmienna – zwracany wskaźnik na zainicjowaną zmienną
atrybuty – wskaźnik na obiekt atrybutów
Działanie: tworzy zmienną warunkową o danych atrybutach.
int pthread_cond_wait (pthread_cond_t *zmienna, pthread_mutex_t *muteks);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu
zmienna – wskaźnik na (zainicjowaną) zmienną warunkową
muteks – wskaźnik na muteks (który powinien być wcześniej zamknięty przez ten wątek)
Działanie: wykonuje niepodzielnie dwie czynności: otwiera dany muteks i zawiesza wątek pod daną
zmienną warunkową. Aby program działał poprawnie, wątek musi wcześniej wywołać
wywołać funkcję zamykającą ten muteks. Powrót z omawianej funkcji następuje po
(niedeterministycznym) uruchomieniu wątku i powoduje automatycznie ponowne
zamknięcie danego muteksu.
int pthread_cond_timedwait (pthread_cond_t *zmienna, pthread_mutex_t *muteks,
const struct timespec *czas);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu
zmienna – wskaźnik na zmienną warunkową
muteks – wskaźnik na muteks
czas – wskaźnik na strukturę określającą maksymalny czas oczekiwania
Działanie: takie samo, jak działanie funkcji pthread_cond_wait ( ), dopóki nie zostanie przekroczony
dany limit czasu, zaś po jego przekroczeniu (z powodu braku obudzenia przez inny wątek)
wątek zostaje ponownie uruchomiony, muteks zamknięty, a funkcja zwraca kod błędu.
int pthread_cond_signal (pthread_cond_t *zmienna);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu
zmienna – wskaźnik na zmienną warunkową
Działanie: uruchamia jeden z wątków (niedeterministycznie wybrany) czekających pod daną zmienną
warunkową (jeśli żaden wątek nie czeka, funkcja nic nie robi).
int pthread_cond_broadcast (pthread_cond_t *zmienna);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu
zmienna – wskaźnik na zmienną warunkową
Działanie: uruchamia wszystkie wątki czekające pod daną zmienną warunkową (jeśli żaden wątek nie
czeka, funkcja nic nie robi).
int pthread_cond_destroy (pthread_cond_t *zmienna);
Zwraca: 0 w przypadku sukcesu
niezerowy kod w przypadku błędu
zmienna – wskaźnik na zmienną warunkową
Działanie: podobnie, jak w przypadku muteksów, jeśli pod zmienną nie czeka żaden wątek, likwiduje
ją i zwraca do systemu zajmowane zasoby (jeśli zmienna była utworzona dynamicznie).
Jeśli zmienna była utworzona statycznie, sprawdza jedynie, czy nie czeka pod nią żaden
wątek.