Sunteți pe pagina 1din 8

Laborator 5- Programare in timp real

Tema: Realizarea de aplicatii real-time in C/Posix sub QNX.


Scop: Mecanisme de sincronizare si comunicare sub QNX / POSIX.
Variabile conditionale.

1.1 Noţiuni teoretice


Fire de execuţie - Mecanisme de sincronizare-Variabile conditionale
În cazul firelor de execuţie POSIX avem la dispoziţie trei mecanisme de sincronizare:
mutexuri, semafoare şi variabile condiţie.

Variabilele condiţie pun la dispoziţie un sistem de notificare pentru fire de execuţie,


permiţându-i unui fir să se blocheze în aşteptarea unui semnal din partea unui alt fir.
Folosirea corectă a variabilelor condiţie presupune un protocol cooperativ între firele de
execuţie.

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.

O variabilă condiţie trebuie întotdeauna folosită împreună cu un mutex pentru evitarea


race-ului care se produce când un fir se pregăteşte să aştepte la variabila condiţie în urma
evaluării predicatului logic, iar alt fir semnalizează variabila condiţie chiar înainte ca
primul fir să se blocheze, pierzându-se astfel semnalul. Aşadar, operaţiile de semnalizare,
testare a condiţiei logice şi blocare la variabila condiţie trebuie efectuate având ocupat
mutexul asociat variabilei condiţie. Condiţia logică este testată sub protecţia mutexului,
iar dacă nu este îndeplinită, firul apelant se blochează la variabila condiţie, eliberând
atomic mutexul. În momentul deblocării, un fir de execuţie va încerca să ocupe mutexul
asociat variabilei condiţie. De asemenea, testarea predicatului logic trebuie făcută într-o
buclă, pentru că dacă sunt eliberate mai multe fire deodată, doar unul va reuşi să ocupe
mutexul asociat condiţiei. Restul vor aştepta ca acesta să-l elibereze, însă este posibil ca
firul care a ocupat mutexul să schimbe valoarea predicatului logic pe durata deţinerii
mutexului. Din acest motiv celelate fire trebuie să testeze din nou predicatul pentru că
altfel şi-ar începe execuţia presupunând predicatul adevărat, când el este, de fapt, fals.

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

o Create threads A and B to do work

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

Iniţializarea unei variabile condiţie

Iniţializarea unei variabile condiţie se realizează prin apelul:

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

sau static, astfel:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

Apelul pthread_cond_init iniţializează variabila condiţie cond cu atributele specificate


prin cond_attr. Pe Linux nu sunt suportate atributele unei variabile condiţie şi deci
parametrul cond_attr va fi ignorat.
Distrugerea unei variabile condiţie

Pentru distrugerea unei variabile condiţie şi eliberarea memoriei ocupate de aceasta, se va


apela funcţia:

int pthread_cond_destroy(pthread_cond_t *cond);

Niciun fir de execuţie nu trebuie să aştepte la variabila condiţie în momentul apelului de


distrugere. În caz contrar, funcţia va întoarce codul de eroare EBUSY. Funcţia va întoarce
0 în caz de succes.

Blocarea la o variabilă condiţie

Pentru a-şi suspenda execuţia şi a aştepta la o variabilă condiţie, un fir de execuţie va


apela:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

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.

Blocarea la o variabilă condiţie cu timeout

Pentru a-şi suspenda execuţia şi a aştepta la o variabilă condiţie, nu mai târziu de un


moment specificat de timp, un fir de execuţie va apela:
int pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);

Funcţia se comportă la fel ca pthread_cond_wait, cu excepţia faptului că dacă variabila


condiţie nu este semnalizată mai devreme de abstime, firul apelant este deblocat, şi după
ocuparea mutexului asociat, funcţia se întoarce cu eroarea ETIMEDOUT. Parametrul
abstime este absolut şi reprezintă numărul de secunde trecute de la 1 ianuarie 1970, ora
00:00.

Deblocarea unui singur fir blocat la o variabilă condiţie

Pentru a debloca un singur fir de execuţie blocat la o variabilă condiţie se va semnaliza


variabila condiţie astfel:

int pthread_cond_signal(pthread_cond_t *cond);

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

Pentru a debloca toate firele de execuţie blocate la o variabilă condiţie, se semnalizează


variabila condiţie astfel:

int pthread_cond_broadcast(pthread_cond_t *cond);

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.

Examplu: Using Condition Variables

Example Code - Using Condition Variables


This simple example code demonstrates the use of several Pthread condition variable
routines. The main routine creates three threads. Two of the threads perform work and
update a "count" variable. The third thread waits until the count variable reaches a
specified value.
In programul principal se creează trei fire. Două fire lucrează si actualizează
valoarea variabilei „count”. Al treilea fir aşteaptă până când variabila „count”
atinge o anumită valoare (COUNT_LIMIT =12).

#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

Basic Management pthread_cond_init

pthread_cond_destroy

pthread_cond_signal

pthread_cond_broadcast

pthread_cond_wait

pthread_cond_timedwait

Condition Variable Attribute Functions

Basic Management pthread_condattr_init

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.

2. Plecand de la exemplu producator consumator, faceti doua fire: un fir genereaza


aleator numere pe care le depune in bufer iar al doilea verifica daca acele numere sunt
prime, afisand true daca numarul este prim si false daca nu e prim.
3. Realizati o aplicatie C/Posix continând 3 fire de execuţie: primul fir citeşte (sau
generează aleator) elementele unui vector, al doilea fir afişează elementele
vectorului, al treilea face ordonarea vectorului si afisarea lui. Ultimele doua fire
vor astepta sa se finalizeze executia primului fir.
4. Realizati o aplicatie C/Posix continând 12 fire de execuţie: primul fir citeşte (sau
generează aleator) elementele unei matrici avand 10 linii si coloane, al doilea fir
afişează elementele matricei, iar restul de 10 fire calculează suma elementelor de
pe linia i (concurent intre ele) calculând în final suma intregii matrici.

S-ar putea să vă placă și