Ir al contenido

Alcance de datos — shared, private, firstprivate, lastprivate, threadprivate, reduction

  • Variables declaradas antes de la región paralela → shared.
  • Variables declaradas dentro del bloque → private de cada hilo.
  • El índice del bucle de un #pragma omp for → private automáticamente.
  • default(shared | none): cambia el comportamiento por defecto. Con default(none) el compilador te obliga a declarar el alcance de cada variable (buena práctica para no olvidar ninguna).

¿Qué x ve cada hilo? Cláusulas de datos

💡 Cada uno recibe una fotocopia del cuaderno original; al salir la bota.

int x = 5;
#pragma omp parallel firstprivate(x)
{
    x += omp_get_thread_num() + 1;   // cada copia empieza en 5
}
printf("%d", x);
fase 0/3
x original (memoria compartida)5hilo 0hilo 1hilo 2hilo 3
Fase 0: Antes: x = 5.
Cláusula¿Copia por hilo?Valor inicial de la copia¿Y al salir?
sharedno, una sola—lo que haya quedado (¡cuidado con las carreras!)
privatesísin inicializarel original no cambia
firstprivatesíel valor originalel original no cambia
lastprivatesísin inicializaroriginal = valor de la última iteración/sección secuencial
reduction(op: x)síel neutro de oporiginal = original op (todas las copias)
threadprivate (directiva, para globales)sí, persistentese inicializa una vezcada hilo conserva su valor en la siguiente región
copyin—copia el valor del maestro a las threadprivate de todos—
copyprivate (con single)—difunde el valor del hilo del single a las privadas de los demás—

Restricción de private: un elemento que es parte de otra variable (un elemento de arreglo o un campo de struct) no puede ir en la lista.

antes de la región: x = 10, s = 5 (del hilo maestro) · 3 hilosshared(x)x = 10una sola copiaal salir:x = lo últimoescritoprivate(x)???al salir:x sigue = 10firstprivate(x)101010al salir:x sigue = 10lastprivate(x)??últ.al salir:x = valor dela última iteraciónreduction(+:s)000al salir:s = s + s₀ + s₁ + s₂rojo = copia sin inicializar (basura) · cada círculo es un hilo con su copia

Cómo leerlo: cada columna es una cláusula y cada círculo de color, un hilo con su copia. Con shared hay una caja para todos. Con private cada hilo recibe una copia sin inicializar (rojo). firstprivate copia el 10 del maestro. lastprivate devuelve al final el valor de la última iteración del orden secuencial. reduction empieza cada copia en el neutro (0 para +) y al final suma todo más el valor original.

sesionb/Ejemp02_private.c
i = 1; j = 2;
//#pragma omp parallel private(i,j)
#pragma omp parallel firstprivate(i,j)
{
i = 3;
j = j + 2;
printf("parallel i: %i j: %i \n", i, j);
}
printf("i: %i j: %i \n", i, j);
parallel i: 3 j: 4 ← ×4 (cada copia de j empezó en 2)
i: 1 j: 2 ← los originales no cambiaron

Con private(i,j) (la línea comentada), j = j + 2 usa una j sin inicializar: el valor dentro es basura. Afuera igual se imprime i: 1 j: 2.

sesionb/Ejemp02_lastprivate.c
int i = 5;
#pragma omp parallel
{
#pragma omp for lastprivate(i)
for (i = 0; i < n-1; i++)
a[i] = b[i] + b[i+1];
}
a[i] = b[i]; /* i == n-1 aquí, como en la versión secuencial */

Con n = 10 y b[i] = 2i: a[0..8] = 2, 6, 10, …, 34 y a[9] = b[9] = 18. Sin lastprivate, después del bucle i valdría 5 (el original) y se escribiría a[5] en vez de a[9].

sesionb/Ejemp02_threadprivate.c
int a = 0, b = 0, i, tid;
float x;
#pragma omp threadprivate(x)
...
omp_set_dynamic(0); // necesario para que x persista
#pragma omp parallel private(b, tid)
{ tid = omp_get_thread_num(); a = tid; b = tid; x = 1.1 * tid + 1.0; ... }
#pragma omp parallel private(tid)
{ tid = omp_get_thread_num(); printf("Thread %d: a,b,x= %d %d %f\n", tid, a, b, x); }
1st Parallel Region: (ordenado aquí; el orden de las líneas varía)
Thread 0: a,b,x= 0 0 1.000000
Thread 1: a,b,x= 1 1 2.100000
Thread 2: a,b,x= 2 2 3.200000
Thread 3: a,b,x= 3 3 4.300000
2nd Parallel Region:
Thread 0: a,b,x= 3 0 1.000000 ← x se conservó por hilo
Thread 1: a,b,x= 3 0 2.100000
Thread 2: a,b,x= 3 0 3.200000
Thread 3: a,b,x= 3 0 4.300000
  • x (threadprivate) se conserva en cada hilo entre regiones.
  • b era private en la región 1: la global nunca cambió → 0.
  • a era shared: los 4 hilos la escribieron a la vez y quedó el valor del último (aquí 3; la lámina muestra 1). Es una carrera: no se puede predecir.

Cada hilo recibe una copia privada inicializada con el neutro del operador; al final se combinan las copias con el operador y se aplican al original.

OperadorValor inicial
+, -, |, ^, ||0
*, &&1
&~0 (todos los bits en 1)
maxel menor valor representable del tipo
minel mayor valor representable del tipo
U4.1 — producto escalar
#pragma omp parallel for default(shared) private(i) schedule(static, chunk) reduction(+:result)
for (i = 0; i < n; i++)
result += a[i] * b[i];
int x = 10;
#pragma omp parallel num_threads(3) firstprivate(x)
{ x += omp_get_thread_num(); printf("%d ", x); }
printf("| %d\n", x);

¿Qué imprime? ¿Y si cambias firstprivate por reduction(+:x)?

Ver solución

Con firstprivate: cada copia empieza en 10 ⇒ imprime 10, 11 y 12 en algún orden, y luego | 10 (el original no cambia).

Con reduction(+:x): cada copia empieza en 0 ⇒ imprime 0, 1 y 2 en algún orden; al final x = 10 + (0 + 1 + 2) ⇒ | 13.

  • Suponer que private copia el valor original: la copia empieza con basura. Para eso está firstprivate.
  • Declarar una variable temporal fuera de la región y usarla dentro sin private: es compartida y hay carrera.
  • Olvidar que en reduction el valor original participa: si s = 5 antes, el resultado es 5 + la suma.
  • Creer que lastprivate deja el valor del último hilo en terminar. Deja el de la última iteración secuencial (la de índice mayor).
int x = 10, s = 5;
#pragma omp parallel for num_threads(2) schedule(static) firstprivate(x) lastprivate(x) reduction(+:s)
for (int i = 0; i < 4; i++) { x += i; s += i; }

¿Cuánto valen x y s al final? ¿Y si se usan 4 hilos?

Ver solución

Con 2 hilos: el hilo 0 hace i = 0, 1 → su x = 10 + 0 + 1 = 11. El hilo 1 hace i = 2, 3 → su x = 10 + 2 + 3 = 15. La última iteración (i = 3) es del hilo 1, así que x = 15. s = 5 + (0 + 1 + 2 + 3) = 11.

Con 4 hilos cada uno hace una iteración: el hilo 3 hace i = 3 → x = 10 + 3 = 13. s sigue siendo 11.

Moraleja: con firstprivate + lastprivate el resultado depende del reparto. reduction no.

Autoevaluación

0/6 correctas · 0 respondidas
1. Una variable private al entrar a la región vale…
2. firstprivate(x) con x = 5: dentro, cada hilo empieza con…
3. Después de un for lastprivate(i) de 0 a n−1, el original i vale…
4. Valor inicial de la copia privada en reduction(*:p):
5. threadprivate se usa para…
6. El índice del bucle de #pragma omp for es…

Fuente ·U4.1 Paralelismo compartido-OMP.pdf (láminas 29–35) · U4.2 Paralelismo compartido-OMP.pdf (private, lastprivate, threadprivate, reduction) · semana_9/sesionb/Ejemp02_private.c · semana_9/sesionb/Ejemp02_lastprivate.c · semana_9/sesionb/Ejemp02_threadprivate.c · semana_9/Ejemp06_reduction.cpp