Documente Academic
Documente Profesional
Documente Cultură
Variabilele condiţie sunt obiecte de sincronizare care-i permit unui fir de execuţie
să-şi suspende execuţia până când o condiţie (predicat logic) devine adevărată. Când
un alt fir de execuţie setează condiţia (predicatul), variabila condiţie va fi semnalizată,
deblocând astfel unul sau toate firele de execuţie blocate la acea variabilă condiţie (în
funcţie de cum se doreşte). Spre deosebire de mutexuri ce implementează sincronizarea
prin controlul accesului thredurilor la date, variabile conditionale permit firelor de
execuţie să se sincronizeze pe baza valorilor lor.
Operaţiile care se pot efectua asupra unei variabile condiţie sunt: semnalizarea variabilei
condiţie când predicatul devine adevărat, blocarea la variabila condiţie în aşteptarea unei
semnalizări, blocarea cu timeout, iniţializarea şi distrugerea.
Functionarea unui program cu mai multe fire ce folosesc variabile conditionale este data
in figura de mai jos:
Main Thread
o Declare and initialize global data/variables which require synchronization (such
as "count")
o Declare and initialize a condition variable object
o Declare and initialize an associated mutex
Thread A Thread B
o Do work up to the point where a o Do work
certain condition must occur (such as o Lock associated mutex
"count" must reach a specified value) o Change the value of the
o Lock associated mutex and check global variable that Thread-A is
value of a global variable waiting upon.
o Call pthread_cond_wait() to o Check value of the global Thread-A
perform a blocking wait for signal from wait variable. If it fulfills the desired
Thread-B. Note that a call to condition, signal Thread-A.
pthread_cond_wait() automatically o Unlock mutex.
and atomically unlocks the associated
mutex variable so that it can be used by o Continue
Thread-B.
o When signalled, wake up. Mutex is
automatically and atomically locked.
o Explicitly unlock mutex
o Continue
Main Thread
Join / Continue
Firul de execuţie apelant trebuie să fi ocupat deja mutexul asociat, în momentul apelului.
Funcţia pthread_cond_wait va elibera mutexul şi se va bloca, aşteptând ca variabila
condiţie să fie semnalizată de un alt fir de execuţie. Cele două operaţii sunt efectuate
atomic. În momentul în care variabila condiţie este semnalizată, se va încerca ocuparea
mutexului asociat, şi după ocuparea acestuia, apelul funcţiei va întoarce. Observaţi că
firul de execuţie apelant poate fi suspendat, după deblocare, în aşteptarea ocupării
mutexului asociat, timp în care predicatul logic, adevărat în momentul deblocării firului,
poate fi modificat de alte fire. De aceea, apelul pthread_cond_wait trebuie efectuat
într-o buclă în care se testează valoarea de adevăr a predicatului logic asociat
variabilei condiţie, pentru a asigura o serializare corectă a firelor de execuţie. Un alt
argument pentru testarea în buclă a predicatului logic este acela că un apel
pthread_cond_wait poate fi întrerupt de un semnal asincron (vezi laboratorul de
semnale), înainte ca predicatul logic să devină adevărat. Dacă firele de execuţie care
aşteptau la variabila condiţie nu ar testa din nou predicatul logic, şi-ar continua execuţia
presupunând greşit că acesta e adevărat. Pentru un exemplu, vezi man
pthread_cond_init. Deoarece la intrarea în funcţie operaţiile de eliberare a mutexului şi
blocare la variabila condiţie sunt efectuate atomic, dacă orice fir care semnalizează
variabila condiţie deţine mutexul asociat, este garantat faptul că semnalizarea nu se
pierde între momentul eliberării mutexului şi cel al blocării la variabila condiţie.
Observaţi şi cum, în lipsa unui mutex asociat variabilei condiţie, ar exista posibilitatea
pierderii unor semnalizări care ar ajunge înainte ca un fir să apuce să aştepte la variabila
condiţie.
Dacă la variabila condiţie nu aşteaptă niciun fir de execuţie, apelul funcţiei nu are efect şi
semnalizarea se va pierde. Dacă la variabila condiţie aşteaptă mai multe fire de execuţie,
va fi deblocat doar unul dintre acestea. Alegerea firului care va fi deblocat este făcută de
planificatorul de fire de execuţie. Nu se poate presupune că firele care aşteaptă vor fi
deblocate în ordinea în care şi-au început aşteptarea. Firul de execuţie apelant trebuie să
deţină mutexul asociat variabilei condiţie în momentul apelului acestei funcţii.
Exemplu:
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count()
{
pthread_mutex_lock(&count_lock);
while (count == 0)
pthread_cond_wait(&count_nonzero, &count_lock);
count = count - 1;
pthread_mutex_unlock(&count_lock);
}
increment_count()
{
pthread_mutex_lock(&count_lock);
if (count == 0)
pthread_cond_signal(&count_nonzero);
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
Deblocarea tututor firelor blocate la o variabilă condiţie
Dacă la variabila condiţie nu aşteaptă niciun fir de execuţie, apelul funcţiei nu are efect şi
semnalizarea se va pierde. Dacă la variabila condiţie aşteaptă fire de execuţie, toate
acestea vor fi deblocate, dar vor concura pentru ocuparea mutexului asociat variabilei
condiţie. Firul de execuţie apelant trebuie să deţină mutexul asociat variabilei condiţie în
momentul apelului acestei funcţii.
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 3
#define TCOUNT 10
#define COUNT_LIMIT 12
int count = 0;
int thread_ids[3] = {0,1,2};
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;
void *inc_count(void *idp)
{
int j,i;
double result=0.0;
int *my_id = idp;
for (i=0; i<TCOUNT; i++) {
pthread_mutex_lock(&count_mutex);
count++;
/*
Check the value of count and signal waiting thread when condition is
reached. Note that this occurs while mutex is locked.
*/
if (count == COUNT_LIMIT) {
pthread_cond_signal(&count_threshold_cv);
printf("inc_count(): thread %d, count = %d Threshold reached.\n",
*my_id, count);
}
printf("inc_count(): thread %d, count = %d, unlocking mutex\n",
*my_id, count);
pthread_mutex_unlock(&count_mutex);
/* Do some work so threads can alternate on mutex lock */
for (j=0; j<1000; j++)
result = result + (double)random();
}
pthread_exit(NULL);
}
void *watch_count(void *idp)
{
int *my_id = idp;
printf("Starting watch_count(): thread %d\n", *my_id);
/*
Lock mutex and wait for signal. Note that the pthread_cond_wait
routine will automatically and atomically unlock mutex while it waits.
Also, note that if COUNT_LIMIT is reached before this routine is run by
the waiting thread, the loop will be skipped to prevent pthread_cond_wait
from never returning.
*/
pthread_mutex_lock(&count_mutex);
if (count<COUNT_LIMIT) {
pthread_cond_wait(&count_threshold_cv, &count_mutex);
printf("watch_count(): thread %d Condition signal
received.\n", *my_id);
}
pthread_mutex_unlock(&count_mutex);
pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
int i, rc;
pthread_t threads[3];
pthread_attr_t attr;
/* Initialize mutex and condition variable objects */
pthread_mutex_init(&count_mutex, NULL);
pthread_cond_init (&count_threshold_cv, NULL);
/* For portability, explicitly create threads in a joinable state */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&threads[0], &attr, inc_count, (void *)&thread_ids[0]);
pthread_create(&threads[1], &attr, inc_count, (void *)&thread_ids[1]);
pthread_create(&threads[2], &attr, watch_count, (void *)&thread_ids[2]);
/* Wait for all threads to complete */
for (i=0; i<NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf ("Main(): Waited on %d threads. Done.\n", NUM_THREADS);
/* Clean up and exit */
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&count_mutex);
pthread_cond_destroy(&count_threshold_cv);
pthread_exit(NULL);
}
Condition Variable Functions
pthread_cond_destroy
pthread_cond_signal
pthread_cond_broadcast
pthread_cond_wait
pthread_cond_timedwait
pthread_condattr_destroy
Sharing pthread_condattr_getpshared
pthread_condattr_setpshared
Aplicatii suplimentare
1. Intrati in directorul prodcons/.
o Inspectati continutul fisierului :prodcons.c.
o In acest exercitiu vom rezolva problema producator-consumator folosind
variabile de conditie si mutecsi. Producatorul va produce un anumit numar
de intregi pe care ii va pune intr-un buffer de lungime limitata de unde
consumatorul ii va lua.
o Completati functiile consumer_fn si producer_fn corespunzatoare
producatorului respectiv consumatorului.
Hints
Folositi tipul buffer_t pentru a reprezenta buffer-ul.Campul
count reprezinta pozitia curenta de inserat in buffer.
Folositi functiile init_buffer , insert_item, remove_item,
is_buffer_full si is_buffer_empty pentru a lucra cu buffer-
ul.