operacje na zmiennych warunkowych
pthread_cond_init, pthread_cond_destroy, pthread_cond_signal,
pthread_cond_broadcast, pthread_cond_wait, pthread_cond_timedwait
Deklaracje
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t
*cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t
*mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t
*mutex, const struct timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
Opis
Zmienne warunkowe sa metoda synchronizacji umozliwiajaca zawieszenie dzialania i odstapienie czasu procesora az do momentu, w ktorym pewien warunek (umieszczony w pamieci dzielonej) nie zostanie spelniony. Najprostszymi operacjami na zmiennych warunkowych sa: zasygnalizowanie spelnienia warunku i oczekiwanie na spelnienie warunku (zawieszenia dzialania watku az do otrzymania sygnalu od innego watku).
Zmienna warunkowa musi byc zawsze otoczona
mutexem, zeby uniknac rownoczesnej proby oczekiwania i sygnalizowania na zmiennej warunkowej. pthread_cond_init inicjalizuje zmienna warunkowa cond uzywajac atrybutow wyspecyfikowanych w cond_attr lub uzywajac domyslnych atrybutow, gdy cond_attr jest ustawione na NULL (aktualnie standard POSIX nie wymaga zadnych atrybutow, wiec wystarczy wstawic w miejsce cond_attr wartosc NULL).Zmienne typu
pthread_cond_t moga byc rowniez inicjalizowane statycznie poprzez uzycie stalej PTHREAD_COND_INITIALIZER. pthread_cond_signal wznawia jeden z watkow oczekujacych na zmiennej warunkowej cond. Jesli zaden watek nie czeka na tej zmiennej, to nic sie nie dzieje. Jesli wiecej niz jeden watek oczekuje na cond, dokladnie jeden jest wznawiany, jednak nie jest wyspecyfikowane ktory. pthread_cond_broadcast wznawia wszystkie watki oczekujace na danej zmiennej warunkowej cond. Nic sie nie dziej, jesli zaden watek nie czeka. pthread_cond_wait w sposob atomowy zwalnia mutex (tak jak przez pthread_unlock_mutex) i oczekuje na sygnal o spelnieniu zmiennej warunkowej cond. Wykonywanie watku jest zawieszane i nie zajmuje czasu procesora az do odebrania sygnalu zmiennej warunkowej. Mutex musi byc zajety przez watek wolajacy pthread_cond_wait. Przed koncem dzialania pthread_cond_wait zajmuje mutex (tak jak przez pthread_lock_mutex).Zwalnianie mutexu i zawieszanie dzialania na zmiennej warunkowej odbywa się atomowo. W zwiazku z tym, jesli wszystkie
watki tuz przed wywolaniem pthread_cond_wait zajmuja mutex jest zagwarantowane, ze warunek nie moze być zasygnalizowany (i zignorowany) pomiedzy zajeciem mutexu i czasem oczekiwania na zmiennej warunkowej. pthread_cond_timedwait atomowo zwalnia mutex i oczekuje na zmienna warunkowa cond, jak pthread_cond_wait, jednak poza tym ustawia odliczanie czasu. Jesli cond nie byl zasygnalizowany w podanym, przez abstime, czasie mutex mutex jest zajmowany i funkcja pthread_cond_timedwait zwraca kod ETIMEDOUT. Parametr abstime okresla bezwzgledny czas podobnie jak funkcje time i gettimeofday (0 odpowiada godz. 00:00:00 GMT, 1 Stycznia 1970) i jest liczony w sekundach. pthread_cond_destroy niszczy zmienna warunkowa zwalniajac zasoby przez nia zajmowane. Zaden watek nie moze czekac na zmiennej w momencie wywolania tej funkcji.Funkcje
pthread_cond_wait i pthread_cond_timedwait sa cancellation points. Jesli watek jest odwolany w momencie, gdy jest zawieszony przez jedna z tych funkcji, watek natychmiast wznawia dzialanie, zajmuje znow mutex i w koncu wykonuje sie odwolanie. Dzieki temu funkcje czyszczace (cleanup handlers) mogą dzialac przy zalozeniu, ze mutex jest zajety w momencie ich wywolania.Funkcje obslugujace zmienne warunkowe nie sa async-signal safe i nie powinny byc wywolywane z procedur obslugi sygnalow. Wywolanie
pthread_cond_signal lub pthread_cond_broadcast z funkcji obslugi sygnalu powoduje blokade (deadlock)wolajacego watku.
Po poprawnym zakonczeniu funkcji obslugi zmiennych warunkowych zwracane jest 0, wpp kod bledu. Funkcja
pthread_cond_timedwait zwraca ETIMEDOUT jeśli zmienna warunkowa nie zostala zasygnalizowana w wyspecyfikowanym czasie(abstime) lub EINTR jesli
pthread_cond_timedwait zostalo przerwane przez sygnal. Funkcja pthread_cond_destroy zwraca EBUSY jesli nastapila proba usuniecia zmiennej warunkowej, na ktora czekaly jakies watki.Przyklad wykorzystania zmiennych warunkowych:
Rozwazmy dwie dzielone zmienne
x i y chronione przez mutex mut i zmienna warunkowa cond, ktora ma byc sygnalizowana za kazdym razem jak x staje się wieksze rowna y.int x,y;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
Oczekiwanie az
x stanie sie wieksze rowne y odbywa sie nastepujaco:pthread_mutex_lock(&mut);
while (x < y) {
pthread_cond_wait(&mut, &cond);
}
/* operate on x and y */
pthread_mutex_unlock(&mut);
Zmiany na
x i y, ktore moga spowodowac, ze x bedzie wiekszy rowny od y powinny byc sygnalizowane na zmiennej warunkowej jesli trzeba:pthread_mutex_lock(&mut);
/* modify x and y */
if (x >= y) pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mut);
Jesli jestesmy pewni, ze co najwyzej jeden z czekajacych watkow musi byc zwolniony (np. jak tylko dwa watki porozumiewaja sie przez x i y), mozna uzyc
pthread_cond_signal jako tansza alternatywa do pthread_cond_broadcast. W razie watpliwosci lepiej uzywac pthread_cond_broadcast.Zeby czekac na moment kiedy
x stanie sie wiekszy rowny od y, ale tylko przez piec sekund:struct timeval now;
struct timespec timeout;
int retcode;
pthread_mutex_lock(&mut);
gettimeofday(&now);
timeout.tv_sec = now.tv_sec + 5;
timeout.tv_nsec = now.tv_usec * 1000;
retcode = 0;
while (x < y && retcode != ETIMEDOUT) {
retcode = pthread_cond_timedwait(&mut, &cond, &timeout);
}
if (retcode == ETIMEDOUT) {
/* timeout occurred */
} else {
/* operate on x and y */
}
pthread_mutex_unlock(&mut);