Descomposició de tasques · 3.6
Protegir problemes de concurrència
Quan molts threads actualitzen la mateixa variable apareixen data races. Veuràs critical, els locks, atomic, la reducció manual i reduction, de menys a més eficient.
Conceptes clau
- Secció crítica (
critical) - Locks (
omp_lock_t) - Operació atòmica (
atomic) - Reducció manual
- Reducció automàtica (
reduction)
En càlcul paral·lel és habitual acumular resultats parcials en una variable compartida (per exemple, un producte escalar). Múltiples threads accedint i modificant simultàniament la mateixa variable provoquen data races. OpenMP ofereix diversos mecanismes per gestionar-ho, ordenats de menor a major eficiència.
Secció crítica (critical)
Garanteix que només un thread executa el bloc cada vegada. És la solució més general però la menys eficient, ja que serialitza tots els accessos, fins i tot quan no hi ha conflicte real.
#pragma omp parallel
{
int val = compute_local(...);
#pragma omp critical
{
insert_sorted(shared_list, val); // operacio arbitraria
}
}
Locks
Ofereixen un control més fi que critical. En lloc de bloquejar globalment un bloc de codi, cada lock s’associa a un recurs concret (per exemple, una posició d’una taula hash), permetent que threads treballin en paral·lel sobre recursos independents. Si tots accedeixen al mateix recurs, el rendiment serà similar a critical.
Per utilitzar-los cal declarar variables de tipus omp_lock_t, inicialitzar-les i destruir-les:
omp_init_lock(&lock): inicialitza el lock.omp_set_lock(&lock): si el lock està lliure, el bloqueja i continua; si està ocupat, el thread queda bloquejat esperant.omp_unset_lock(&lock): allibera el lock perquè un altre thread el pugui adquirir.omp_destroy_lock(&lock): destrueix el lock.
omp_lock_t v_locks[M]; // un lock per cada recurs
for (int i = 0; i < M; i++)
omp_init_lock(&v_locks[i]);
#pragma omp parallel
{
while (...) {
int index = ...;
omp_set_lock(&v_locks[index]); // bloqueja index
function(v[index], ...); // acces protegit
omp_unset_lock(&v_locks[index]); // desbloqueja index
}
}
for (int i = 0; i < M; i++)
omp_destroy_lock(&v_locks[i]);
Operació atòmica (atomic)
Garanteix que una operació d’actualització simple sigui indivisible. Només funciona per a operacions bàsiques (+, -, *, /, &, |, ^, <<, >>). Menys sobrecost que critical, però genera una operació atòmica per cada actualització, de manera que amb molts threads sobre la mateixa variable la contenció pot ser elevada.
int count = 0;
#pragma omp parallel
{
// ... cada thread fa feina ...
if (found_match(...)) {
#pragma omp atomic
count++; // actualitzacio atomica puntual
}
}
Reducció manual
Cada thread acumula en una variable local (privada) i fa una única operació atòmica al final per combinar el resultat parcial amb el global. Redueix les operacions atòmiques de iteracions al nombre de threads.
int total = 0;
#pragma omp parallel
{
int id = omp_get_thread_num();
int nt = omp_get_num_threads();
int BS = N / nt;
int total_local = 0;
for (int i = id * BS; i < (id + 1) * BS; i++) {
total_local += v[i] * w[i];
}
#pragma omp atomic
total += total_local; // una unica atomica per thread
}
Reducció automàtica (reduction)
La clàusula reduction(op:var) és la solució més neta i eficient. OpenMP gestiona automàticament les còpies privades i les combina al final. Operadors suportats: +, -, *, /, &, |, ^, max, min.
Per a tasques explícites amb task (no taskloop), cal usar task_reduction (al pare) + in_reduction (a cada tasca filla).
int total = 0;
#pragma omp parallel
#pragma omp single
{
#pragma omp taskloop reduction(+: total)
for (int i = 0; i < N; i++)
total += v[i] * w[i]; // OpenMP gestiona les copies privades
}
Comparativa
| Mètode | Sobrecost | Escalabilitat | Ús típic |
|---|---|---|---|
critical |
Molt alt | Baixa | Accés genèric a recursos |
| Locks | Alt | Mitjana | Recursos independents (taula hash) |
atomic |
Mitjà | Baixa | Operacions simples sobre una variable |
| Reducció manual | Baix | Alta | Càlculs acumulatius |
| Reducció automàtica | Baix | Alta | Sumes, màxims, mínims… |