This is an old revision of the document!
Uneori dorim să distribuim ca thread-uri diferite să execute task-uri diferite în același timp. În această privință ne vine de ajutor conceptul de sections, prin care două sau mai multe thread-uri execută două sau mai multe sections corespunzătoare acestora (adică thread-urilor, fiecare thread cu un section).
În OpenMP se folosește directiva sections
pentru a marca o zonă din cod în care distribuim task-urile diferite (sections) thread-urilor (fiecare thread cu câte un section). Sintaxa în OpenMP este următoarea:
#pragma omp parallel { // se marchează blocul de sections #pragma omp sections { #pragma omp section { // section executat de thread-ul X } #pragma omp section { // section executat de thread-ul Y } #pragma omp section { // section executat de thread-ul Z } } #pragma omp sections { #pragma omp section { // section executat de thread-ul X } #pragma omp section { // section executat de thread-ul Y } } } #pragma omp parallel sections { #pragma omp section { // section executat de thread-ul X } #pragma omp section { // section executat de thread-ul Y } }
Dacă dorim ca o secvență de cod (dintr-o bucată de cod paralelizat) să fie executat doar de un singur thread, folosim directiva SINGLE
. Aceasta este folosită, de regulă, în operații I/O.
Exemplu:
#pragma omp parallel { #pragma omp single { // cod executat de un singur thread } }
Directiva MASTER
este o particularizare a directivei SINGLE
, unde codul din zona paralelizată este executat de thread-ul master (cel cu id-ul 0).
#pragma omp parallel { #pragma omp master { // cod executat de un singur thread } }
Pentru zonele critice, unde avem operații de read-write, folosim directiva #pragma omp critical
, care reprezintă un mutex, echivalentul lui pthread_mutex_t
din pthreads, care asigură faptul că un singur thread accesează zona critică la un moment dat, thread-ul deținând lock-ul pe zona critică în momentul respectiv, și că celelalte thread-uri care nu au intrat încă în zona critică așteaptă eliberarea lock-ului de către thread-ul aflat în zona critică în acel moment.
Exemplu de folosire:
#include <stdio.h> #include <omp.h> int main (int argc, char** argv) { int thread_id, sum = 0; #pragma omp parallel private(thread_id) shared(sum) { thread_id = omp_get_thread_num(); #pragma omp critical sum += thread_id; } printf("%d",sum); return 0; }
Un alt element de sincronizare reprezintă bariera, care asigură faptul că niciun thread gestionat de barieră nu trece mai departe de aceasta decât atunci cand toate thread-urile gestionate de barieră au ajuns la punctul unde se află bariera.
În OpenMP, pentru barieră avem directiva #pragma omp barrier
, echivalent cu pthread_barrier_t
din pthreads.
Exemplu de folosire:
#include <stdio.h> #include <omp.h> int main (int argc, char** argv) { #pragma omp parallel { printf("First print by %d\n", omp_get_thread_num()); #pragma omp barrier printf("Second print by %d\n", omp_get_thread_num()); } return 0; }
reduction
este o directivă folosită pentru operații de tip reduce / fold pe arrays / colecții sau simple însumări / înmulțiri în cadrul unui loop. Mai precis, elementele dintr-un array sau indecșii unui loop sunt “acumulați” într-o singură variabilă, cu ajutorul unei operații, al cărui semn este precizat.
Tipar: reduction(operator_operatie:variabila_in_care_se_acumuleaza)
Exemplu de reduction: reduction(+:sum)
, unde se însumează elementele unui array în variabila sum
Exemplu de folosire de reduction:
int sum = 0; #pragma omp parallel for reduction(+:sum) private(i) for (i = 1; i <= num_steps; i++) { sum += i; }
Directiva ATOMIC
permite executarea unor instrucțiuni în mod atomic, instrucțiuni care provoacă race conditions între thread-uri, problemă pe care această directivă o rezolvă.
Exemplu de folosire:
#include <stdio.h> #include <omp.h> int main (int argc, char** argv) { int thread_id, sum = 0; #pragma omp parallel private(thread_id) shared(sum) { thread_id = omp_get_thread_num(); #pragma omp atomic sum += thread_id; } printf("%d",sum); return 0; }
Directiva ORDERED
este folosit în for-uri cu scopul de a distribui în ordine iterațiile către thread-uri.
Exemplu:
#pragma omp parallel for ordered private(i) for (i = 0; i < 10; i++) { printf("** iteration %d thread no. %d\n", i, omp_get_thread_num()); }
Afișare:
** iteration 9 thread no. 7 ** iteration 5 thread no. 3 ** iteration 6 thread no. 4 ** iteration 7 thread no. 5 ** iteration 4 thread no. 2 ** iteration 2 thread no. 1 ** iteration 3 thread no. 1 ** iteration 0 thread no. 0 ** iteration 1 thread no. 0 ** iteration 8 thread no. 6
SHARED
PRIVATE
- variabilă cu valoarea vizibilă doar în blocul paralel (diferă de THREADPRIVATE
)DEFAULT
REDUCTION
NONE
THREADPRIVATE
- fiecare thread are propriile sale copii ale unor variabileFIRSTPRIVATE
- folosit pentru ca variabilele THREADPRIVATE
să aibă valorile, inițial, din exterior (dinainte)LASTPRIVATE
- invers FIRSTPRIVATE
, ultima valoare asignată unei variabile THREADPRIVATE
e vizibilă după blocul paralelizatCOPYPRIVATE
- folosit în blocurile SINGLE
, pentru a face vizibilă valoarea atribuită unei variabile într-un bloc SINGLE
pentru toate thread-urileCOPYIN
- asignarea unei variabile THREADPRIVATE
este vizibilă tuturor thread-urilor