Sunteți pe pagina 1din 9

Laborator 3- Programare in timp real

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


Scop: Mecanisme de sincronizare si comunicare sub QNX / POSIX.
Mutex-uri.

1.1 Noţiuni teoretice

Fire de execuţie - Mecanisme de sincronizare


În cazul firelor de execuţie POSIX avem la dispoziţie trei mecanisme de sincronizare: mutexuri,
semafoare şi variabile condiţie. Similar, în cazul firelor de execuţie POSIX, primitiva pthread_join
permite aşteptarea terminării unui alt fir de execuţie.

În Windows nu există un obiect de sincronizare omolog variabilelor condiţie POSIX, dar sunt posibile
diverse soluţii de implementare cu rezultate similare.

Mecanisme de sincronizare a firelor de execuţie POSIX


Mecanismele de sincronizare disponibile pentru firele de execuţie POSIX sunt mutexurile, semafoarele
şi variabilele condiţie.

Mutexuri

Mutexurile sunt obiecte de sincronizare utilizate pentru a asigura accesul exclusiv la o secţiune de
cod în care se accesează date partajate între două sau mai multe fire de execuţie. Operaţiile care se pot
efectua asupra unui mutex sunt cele de ocupare, eliberare, încercare neblocantă de ocupare, iniţializare
şi distrugere. Un mutex are două stări posibile: ocupat şi liber. Un mutex poate fi ocupat de un singur
fir de execuţie la un moment dat. Atunci când un mutex este ocupat de un fir de execuţie, el nu mai
poate fi ocupat de niciun altul. În acest caz, o cerere de ocupare venită din partea unui alt fir, în general
va bloca firul până în momentul în care mutexul devine liber.

Pentru serializarea accesului la o secţiune critică, fiecare fir de execuţie va trebui să ocupe mutexul
asociat secţiunii respective la începutul ei şi să-l elibereze la ieşirea din secţiune. În acest fel, dacă două
fire de execuţie ajung simultan la secţiunea critică (încearcă să-i ocupe mutexul simultan), doar unul
dintre ele va reuşi, şi va intra în secţiunea critică, iar celălalt se va bloca până când mutexul va fi
eliberat. Primul fir va elibera mutexul la sfârşitul secţiunii, permiţându-i celuilalt fir să-l ocupe, cedând
astfel accesul la secţiunea critică.

Iniţializarea unui mutex

Un mutex se iniţializează prin apelul funcţiei:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

care iniţializează mutex cu atributele specificate prin parametrul attr. Dacă attr este NULL se va
iniţializa mutexul cu atributele implicite.
Iniţializarea atributelor unui mutex se realizează prin apelul funcţiei:

int pthread_mutexattr_init(pthread_mutexattr_t *attr);

Distrugerea atributelor unui mutex se realizează prin apelul funcţiei:

int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

LinuxThreads suportă un singur atribut şi anume tipul de mutex care poate fi:

 PTHREAD_MUTEX_FAST_NP;
 PTHREAD_MUTEX_RECURSIVE_NP;
 PTHREAD_MUTEX_ERRORCHECK_NP;

Atributul tip al unui mutex este setat prin apelul funcţiei:

int pthread_mutexattr_setkind_np(pthread_mutexattr_t *attr, int kind);

Tipul unui mutex dictează comportarea în momentul în care un fir de execuţie încearcă să ocupe un
mutex pe care îl ocupa deja. În cazul unui mutex de tip FAST, firul de execuţie va aştepta la infinit
eliberarea mutexului. În cazul unui mutex RECURSIVE, firul va ocupa cu succes mutexul a doua oară.
Pentru a trece mutexul în starea liber, threadul ocupant va trebui să-l elibereze de acelaşi număr de ori
de care l-a ocupat (de 2 ori în cazul nostru). În cazul unui mutex ERRORCHECK, o a doua încercare de
ocupare va eşua cu errno setat pe EDEADLK. Tipul implicit de mutex este FAST.

Un mutex poate fi iniţializat şi static, prin atribuirea uneia din constantele:

 PTHREAD_MUTEX_INITIALIZER;
 PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
 PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP.

Distrugerea unui mutex

Pentru distrugerea unui mutex şi eliberarea memoriei ocupate de acesta se va utiliza funcţia:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

Mutexul trebuie să fie liber pentru a putea fi distrus. În caz contrar funcţia va întoarce codul de eroare
EBUSY. Întoarcerea valorii 0 semnifică succesul apelului.

Ocuparea unui mutex

Pentru ocuparea unui mutex se va apela funcţia:

int pthread_mutex_lock(pthread_mutex_t *mutex);

Dacă mutexul este liber în momentul apelului, acesta va fi ocupat de firul apelant şi funcţia va întoarce
imediat. Dacă mutexul este ocupat de un alt fir, apelul va bloca până la eliberarea mutexului. Dacă
mutexul este ocupat de firul curent, comportamentul funcţiei este dictat de tipul mutexului.
Nu este garantată o ordine FIFO de ocupare a unui mutex. Oricare din firele aflate în aşteptare la
deblocarea unui mutex pot să-l acapareze.

Încercarea neblocantă de ocupare a unui mutex

Pentru a încerca ocuparea unui mutex fără a aştepta eliberarea acestuia în cazul în care este deja ocupat,
se va apela funcţia:

int pthread_mutex_trylock(pthread_mutex_t *mutex);

Această funcţie se comportă la fel ca pthread_mutex_lock, cu excepţia faptului că nu va bloca firul de


execuţie apelant, ci va întoarce codul de eroare EBUSY când este cazul.

Eliberarea unui mutex

Eliberarea unui mutex se realizează prin apelul funcţiei:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Dacă mutexul este de tip FAST, el va trece în starea liber. Unuia de tip RECURSIVE i se va decrementa
contorul intern de ocupări, trecând în starea liber doar când acest contor ajunge la 0. În cazul în care
mutexul este de tip ERRORCHECK se va testa mai întâi dacă mutexul era în starea ocupat şi dacă era
ocupat de firul de execuţie care a apelat funcţia de eliberare. Dacă aceste condiţii nu sunt îndeplinite
funcţia întoarce un cod de eroare, iar starea mutexului rămâne neschimbată.

Iată şi un exemplu de folosire a unui mutex:

#include <stdio.h>
#include <pthread.h>

#define NUM_THREADS 5

pthread_mutex_t mutex;
int global_var = 0;

void *thread_routine(void *arg);

int main(void)
{
int i;
pthread_t tids[NUM_THREADS];

pthread_mutex_init(&mutex, NULL);

for (i = 0; i < NUM_THREADS; ++i) {


pthread_create(&tids[i], NULL, thread_routine, (void *)i);
}

for (i = 0; i < NUM_THREADS; ++i) {


pthread_join(tids[i], NULL);
}

pthread_mutex_destroy(&mutex);
return 0;
}

void *thread_routine(void *arg)


{
pthread_mutex_lock(&mutex);

printf("Thread %d says global_var=%d\n", (int)arg, global_var++);

pthread_mutex_unlock(&mutex);

pthread_exit(NULL);
return NULL;
}

În exemplul de mai sus, mutex protejează accesul la variabila globală global_var.

Mutex-ul (engl. mutual exclusion lock) reprezinta cel mai simplu mecanism de sincronizare.
Un mutex este folosit pentru a asigura accesul exclusiv la o zona de date folosita in comun de mai
multe fire de executie. Un mutex se poate afla in doua stari:
incuiat (engl. locked), prin apelul functiei pthread_mutex_lock (se specifica in acest fel
inceputul sectiunii critice);
liber (engl. unlocked), prin apelul functiei pthread_mutex_unlock (specificand sfarsitul
sectiunii critice).
Un singur fir de executie poate incuia un mutex la un moment dat. Firele de executie care
incearca sa incuie un mutex deja incuiat se vor bloca pana cand firul de executie va fi deblocat.
Cand firul de executie elibereaza mutexul, se va debloca urmatorul fir de executie (in ordinea
prioritatii) care astepta eliberarea lui, va incuia mutexul si va deveni proprietarul acestuia. In acest
fel, firele de executie se succed prin regiunea critica in ordinea prioritatii.
Testarea starii unui mutex fara blocarea firului de executie se realizeaza cu functia
pthread_mutex_trylock. Daca un fir de executie cu o prioritate mai mare decat proprietarul
mutex-ului incearca sa-l acapareze, prioritatea efectiva a proprietarului mutex-ului va fi crescuta
pana la valoarea prioritatii firului de executie ce incearca sa-l acapareze. Proprietarul va reveni la
prioritatea initiala dupa ce va elibera mutex-ul.
Proprietatile unui mutex pot fi modificate folosind functia pthread_mutex_setrecursive,
permitand astfel respectivului mutex sa fie incuiat in mod recursiv de catre acelasi fir de executie.
Acest lucru este util atunci cand firul de executie lanseaza o rutina care incearca sa incuie un mutex
pe care el il detine deja.

Aplicatie Example: Using Mutexes

Example Code - Using Mutexes


This example program illustrates the use of mutex variables in a threads program
that performs a dot product. The main data is made available to all threads through a
globally accessible structure. Each thread works on a different part of the data. The
main thread waits for all the threads to complete their computations, and then it
prints the resulting sum.

#include <pthread.h>
#include <stdio.h>
#include <malloc.h>
/*
The following structure contains the necessary information
to allow the function "dotprod" to access its input data and
place its output into the structure.
*/
typedef struct
{
double *a;
double *b;
double sum;
int veclen;
} DOTDATA;
/* Define globally accessible variables and a mutex */
#define NUMTHRDS 4
#define VECLEN 100
DOTDATA dotstr;
pthread_t callThd[NUMTHRDS];
pthread_mutex_t mutexsum;
/*
The function dotprod is activated when the thread is created.
All input to this routine is obtained from a structure
of type DOTDATA and all output from this function is written into
this structure. The benefit of this approach is apparent for the
multi-threaded program: when a thread is created we pass a single
argument to the activated function - typically this argument
is a thread number. All the other information required by the
function is accessed from the globally accessible structure.
*/
void *dotprod(void *arg)
{
/* Define and use local variables for convenience */
int i, start, end, offset, len ;
double mysum, *x, *y;
offset = (int)arg;

len = dotstr.veclen;
start = offset*len;
end = start + len;
x = dotstr.a;
y = dotstr.b;
/*
Perform the dot product and assign result
to the appropriate variable in the structure.
*/
mysum = 0;
for (i=start; i<end ; i++)
{
mysum += (x[i] * y[i]);
}
/*
Lock a mutex prior to updating the value in the shared
structure, and unlock it upon updating.
*/
pthread_mutex_lock (&mutexsum);
dotstr.sum += mysum;
pthread_mutex_unlock (&mutexsum);
pthread_exit((void*) 0);
}
/*
The main program creates threads which do all the work and then
print out result upon completion. Before creating the threads,
the input data is created. Since all threads update a shared structure,
we need a mutex for mutual exclusion. The main thread needs to wait for
all threads to complete, it waits for each one of the threads. We specify
a thread attribute value that allow the main thread to join with the
threads it creates. Note also that we free up handles when they are
no longer needed.
*/
int main (int argc, char *argv[])
{
int i;
double *a, *b;
int status;
pthread_attr_t attr;
/* Assign storage and initialize values */
a = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double));
b = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double));

for (i=0; i<VECLEN*NUMTHRDS; i++)


{
a[i]=1.0;
b[i]=a[i];
}
dotstr.veclen = VECLEN;
dotstr.a = a;
dotstr.b = b;
dotstr.sum=0;
pthread_mutex_init(&mutexsum, NULL);

/* Create threads to perform the dotproduct */


pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for(i=0; i<NUMTHRDS; i++)
{
/*
Each thread works on a different set of data.
The offset is specified by 'i'. The size of
the data for each thread is indicated by VECLEN.
*/
pthread_create( &callThd[i], &attr, dotprod, (void *)i);
}
pthread_attr_destroy(&attr);
/* Wait on the other threads */
for(i=0; i<NUMTHRDS; i++)
{
pthread_join( callThd[i], (void **)&status);
}
/* After joining, print out the results and cleanup */
printf ("Sum = %f \n", dotstr.sum);
free (a);
free (b);
pthread_mutex_destroy(&mutexsum);
pthread_exit(NULL);
}

Exemplul 3
Programul va scrie la iesirea standard toate numerele prime in domeniul 2 si n, unde n este data ca
argument linie comanda Se creează n-1 threads, fiecare fiind responsabil cu determinarea faptului ca
valoarea primita este sau nu prima. In acest exemplu se prezinta o solutie de a accesa exclusiv iesirea
standard pentru a scrie rezultatul.

Java
Java permite o solutie foarte eleganta de sincronizare a accesului la un bloc de date folosind o
declaratie synchronized pe acel obiect ce vrem sa il blocam pe durata scrierii..

/**
* A utility class containing a method to print all primes up to
* a given integer. The method internally invokes several threads
* to do its work, one for each candidate prime.
*/
public class PrimePrinter {

/**
* Prints all primes upto and including n. Internally it spawns threads
* for testing the primality of 2..n. Each of the threads will print
* the value it is testing iff it is prime.
*/
public static void printPrimes(int n) {
for (int i = 2; i <= n; i++) {
final int candidate = i;
Thread thread = new Thread() {
public void run() {
for (int divisor = 2; divisor <= candidate; divisor++) {
if (divisor == candidate) {
synchronized (System.out) {
System.out.print(" " + candidate);
}
} else if (candidate % divisor == 0) {
break;
}
}
}
};
thread.start();
}
}
/**
* Executes printPrimes on its sole command line argument.
*/
public static void main(String args[]) {
if (args.length != 1) {
System.out.println("Needs one integer parameter");
} else {
try {
printPrimes(Integer.parseInt(args[0]));
} catch (NumberFormatException e) {
System.out.println("Argument must be an integer");
}
}
}
}

C++ with Posix Threads

Se folosesc variabile mutex de tip pthreads. Spre deosebire de Ada, Java, and Perl, unlocking is done
with a library call. Don't forget it!

/*****************************************************************************
*
* primes.cpp
*
* This program prints all primes up to and including its integer argument
* n. It works by running threads for all values k in 2..n which determine
* if k is prime and print k if it is.
*
*****************************************************************************/

#include <iostream>
#include <pthread.h>

using namespace std;

#define TRY(code, message) if (code) {cout << message << '\n'; exit(-1);}

// The mutex to use to print safely.


pthread_mutex_t writing;

// Takes in an integer and prints it if and only if it is prime.


// The printing needs to be done within a critical section.
void* check(void* candidate) {
for (int divisor = 2; divisor <= (int)candidate; divisor++) {
if (divisor == (int)candidate) {
TRY(pthread_mutex_lock(&writing), "Can't lock mutex");
cout << (int)candidate << ' ';
TRY(pthread_mutex_unlock(&writing), "Can't unlock mutex");
} else if ((int)candidate % divisor == 0) {
break;
}
}
return NULL;
}
// Prints primes up to n. It stores all the threads in an array so
// that it can wait on them all to finish. Then it cleans up.
void findPrimes(int n) {
pthread_t* checkers;

if (n < 2) return;

checkers = (pthread_t*)malloc((n-1) * sizeof(pthread_t));


TRY(pthread_mutex_init(&writing, NULL), "Can't create mutex");
for (int i = 2; i <= n; i++) {
TRY(pthread_create(&checkers[i-2], NULL, &check, (void*)i),
"Cannot create one or more of the threads");
}

for (int i = 0; i < n-1; i++) {


int ignored;
TRY(pthread_join(checkers[i], (void**)&ignored), "Join failed");
}

TRY(pthread_mutex_destroy(&writing), "Can't destroy mutex");


free(checkers);
}

// Invokes findPrimes with a command line argument.


int main(int argc, char** argv) {
if (argc != 2) {
cout << "This application requires exactly one parameter\n";
} else {
findPrimes(atoi(argv[1]));
}
return 0;
}

Aplicatia 3: Plecand de la exemplul 1 realizati o aplicatie cu 2 fire de executie, ce pentru un


vector cititi in main(), calculeaza suma elementelor. Primul fir va calcula suma partiala din
elementele de pe pozitiile pare, iar al doilea fir suma partiala din elementele de pe pozitia
inmpara. In final se obtine suma finala.

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