Compartir vía


A Ejemplos

A continuación se muestran ejemplos de las construcciones que se definen en este documento. Solo se compone una instrucción que sigue a una directiva solo en caso necesario y se aplica una sangría a una instrucción no compuesta después de una directiva anterior.

A.1 Un bucle sencillo en paralelo

En el ejemplo siguiente se muestra cómo se paraleliza un bucle mediante la directiva paralela para. La variable de iteración de bucles es privada de forma predeterminada, por lo que no es necesario especificarlo explícitamente en una cláusula privada.

#pragma omp parallel for
    for (i=1; i<n; i++)
        b[i] = (a[i] + a[i-1]) / 2.0;

A.2 Compilación condicional

En los ejemplos siguientes se muestra el uso de la compilación condicional mediante la macro OpenMP _OPENMP. Con la compilación de OpenMP, se define la macro _OPENMP.

# ifdef _OPENMP
    printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif

El operador del preprocesador definido permite probar más de una macro en una sola directiva.

# if defined(_OPENMP) && defined(VERBOSE)
    printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif

A.3 Regiones paralelas

La directiva paralela se puede usar en programas paralelos generales. En el ejemplo siguiente, cada subproceso de la región paralela decide en qué parte de la matriz global x funcionará basándose en el número del subproceso:

#pragma omp parallel shared(x, npoints) private(iam, np, ipoints)
{
    iam = omp_get_thread_num();
    np =  omp_get_num_threads();
    ipoints = npoints / np;
    subdomain(x, iam, ipoints);
}

A.4 La cláusula nowait

Si existen muchos bucles independientes en una región paralela, puede usar la cláusula nowait para evitar la barrera implícita al final de la directiva for, como se indica a continuación:

#pragma omp parallel
{
    #pragma omp for nowait
        for (i=1; i<n; i++)
             b[i] = (a[i] + a[i-1]) / 2.0;
    #pragma omp for nowait
        for (i=0; i<m; i++)
            y[i] = sqrt(z[i]);
}

A.5 La directiva crítica

En el siguiente ejemplo se incluyen varias directivas críticas. En el ejemplo se muestra un modelo de puesta en cola en el que una tarea se retira de la cola y funciona. Para protegerse frente a muchos subprocesos que retiran de la cola la misma tarea, la operación de retirada de la cola debe estar en una sección critical. Dado que las dos colas de este ejemplo son independientes, se protegen mediante directivas critical con distintos nombres, xaxis y yaxis.

#pragma omp parallel shared(x, y) private(x_next, y_next)
{
    #pragma omp critical ( xaxis )
        x_next = dequeue(x);
    work(x_next);
    #pragma omp critical ( yaxis )
        y_next = dequeue(y);
    work(y_next);
}

A.6 La cláusula lastprivate

La ejecución correcta a veces depende del valor que asigna la última iteración de un bucle a una variable. Estos programas deben enumerar todas las variables como argumentos a una cláusula lastprivate para que los valores de las variables coincidan cuando el bucle se ejecuta secuencialmente.

#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];

En el ejemplo anterior, el valor de i al final de la región paralela será igual a n-1, como en el caso secuencial.

A. 7 La cláusula de reducción

En el ejemplo siguiente demuestra la cláusula de reducción:

#pragma omp parallel for private(i) shared(x, y, n) \
                         reduction(+: a, b)
    for (i=0; i<n; i++) {
        a = a + x[i];
        b = b + y[i];
    }

A.8 Secciones paralelas

En el ejemplo siguiente, (en la sección 2.4.2), las funciones xaxis, yaxis y zaxis pueden ejecutarse simultáneamente. La primera directiva section es opcional. Todas las directivas section tienen que aparecer en la extensión léxica de la construcción parallel sections.

#pragma omp parallel sections
{
    #pragma omp section
        xaxis();
    #pragma omp section
        yaxis();
    #pragma omp section
        zaxis();
}

A.9 Directivas únicas

En el siguiente ejemplo se muestra la directiva única. En el ejemplo, solo un subproceso, (normalmente el primer subproceso que encuentra la directiva single), imprime el mensaje de progreso. No se deben hacer suposiciones sobre qué subproceso ejecutará la sección single. Todos los demás subprocesos omitirán la sección single y se detendrán en la barrera al final de la construcción single. Si otros subprocesos pueden continuar sin esperar a que el subproceso ejecute la sección single, puede especificarse nowait en la directiva single.

#pragma omp parallel
{
    #pragma omp single
        printf_s("Beginning work1.\n");
    work1();
    #pragma omp single
        printf_s("Finishing work1.\n");
    #pragma omp single nowait
        printf_s("Finished work1 and beginning work2.\n");
    work2();
}

A.10 Ordenación secuencial

Las secciones ordenadas resultan útiles para ordenar secuencialmente la salida del trabajo que se hace en paralelo. El programa siguiente imprime los índices en un orden secuencial:

#pragma omp for ordered schedule(dynamic)
    for (i=lb; i<ub; i+=st)
        work(i);
void work(int k)
{
    #pragma omp ordered
        printf_s(" %d", k);
}

A.11 Un número fijo de subprocesos

Algunos programas dependen de un número fijo y especificado de subprocesos para ejecutarse de forma correcta. Dado que la configuración predeterminada del ajuste dinámico del número de subprocesos está definida por la implementación, estos programas pueden elegir desactivar la capacidad de subproceso y establecer el número de subprocesos explícitamente para mantener la portabilidad. Los ejemplos siguientes ilustran cómo hacerlo con omp_set_dynamic y omp_set_num_threads:

omp_set_dynamic(0);
omp_set_num_threads(16);
#pragma omp parallel shared(x, npoints) private(iam, ipoints)
{
    if (omp_get_num_threads() != 16)
      abort();
    iam = omp_get_thread_num();
    ipoints = npoints/16;
    do_by_16(x, iam, ipoints);
}

En este ejemplo, el programa solo se ejecuta correctamente si dieciséis subprocesos lo ejecutan. Si la implementación no puede admitir dieciséis subprocesos, el comportamiento de este ejemplo se define por la implementación.

El número de subprocesos que ejecutan una región paralela se mantiene constante durante una región paralela, independientemente de la configuración de subprocesos dinámicos. El mecanismo de subprocesos dinámicos determina el número de subprocesos que se van a usar al inicio de la región paralela y lo mantiene constante mientras dura la región.

A.12 La directiva atómica

En el ejemplo siguiente se evitan las condiciones de carrera, (actualizaciones simultáneas de un elemento de x por muchos subprocesos), al usar la directiva atómica:

#pragma omp parallel for shared(x, y, index, n)
    for (i=0; i<n; i++)
    {
        #pragma omp atomic
            x[index[i]] += work1(i);
        y[i] += work2(i);
    }

La ventaja de usar la directiva atomic en este ejemplo es que permite que las actualizaciones de dos elementos distintos de x se produzcan en paralelo. Si, en su lugar, se usa una directiva crítica, todas las actualizaciones a elementos de x se ejecutan de forma serial, (aunque no en un orden asegurado).

La directiva atomic solo se aplica a la instrucción C o C++ inmediatamente a continuación de ella. Como resultado, los elementos de y no se actualizan atómicamente en este ejemplo.

A.13 Una directiva flush con una lista

En el ejemplo siguiente se usa la directiva flush para una sincronización de punto a punto de objetos específicos entre pares de subprocesos:

int   sync[NUMBER_OF_THREADS];
float work[NUMBER_OF_THREADS];
#pragma omp parallel private(iam,neighbor) shared(work,sync)
{
    iam = omp_get_thread_num();
    sync[iam] = 0;
    #pragma omp barrier

    // Do computation into my portion of work array
    work[iam] = ...;

    //  Announce that I am done with my work
    // The first flush ensures that my work is
    // made visible before sync.
    // The second flush ensures that sync is made visible.
    #pragma omp flush(work)
    sync[iam] = 1;
    #pragma omp flush(sync)

    // Wait for neighbor
    neighbor = (iam>0 ? iam : omp_get_num_threads()) - 1;
    while (sync[neighbor]==0)
    {
        #pragma omp flush(sync)
    }

    // Read neighbor's values of work array
    ... = work[neighbor];
}

A.14 Una directiva flush sin una lista

En el ejemplo siguiente, (para la sección 2.6.5), se distinguen los objetos compartidos afectados por una directiva flush sin una lista de objetos compartidos que no se ven afectados:

// omp_flush_without_list.c
#include <omp.h>

int x, *p = &x;

void f1(int *q)
{
    *q = 1;
    #pragma omp flush
    // x, p, and *q are flushed
    //   because they are shared and accessible
    // q is not flushed because it is not shared.
}

void f2(int *q)
{
    #pragma omp barrier
    *q = 2;

    #pragma omp barrier
    // a barrier implies a flush
    // x, p, and *q are flushed
    //   because they are shared and accessible
    // q is not flushed because it is not shared.
}

int g(int n)
{
    int i = 1, j, sum = 0;
    *p = 1;

    #pragma omp parallel reduction(+: sum) num_threads(10)
    {
        f1(&j);
        // i, n and sum were not flushed
        //   because they were not accessible in f1
        // j was flushed because it was accessible
        sum += j;
        f2(&j);
        // i, n, and sum were not flushed
        //   because they were not accessible in f2
        // j was flushed because it was accessible
        sum += i + j + *p + n;
    }
    return sum;
}

int main()
{
}

A.15 El número de subprocesos empleados

Considere el ejemplo incorrecto siguiente, (para la sección 3.1.2):

np = omp_get_num_threads(); // misplaced
#pragma omp parallel for schedule(static)
    for (i=0; i<np; i++)
        work(i);

La llamada a omp_get_num_threads() devuelve 1 en la sección de serie del código, por lo que np siempre será igual a 1 en el ejemplo anterior. Para determinar el número de subprocesos que se implementarán en la región paralela, la llamada debe ser en la región paralela.

En el ejemplo siguiente se expone cómo volver a escribir este programa sin incluir una consulta para el número de subprocesos:

#pragma omp parallel private(i)
{
    i = omp_get_thread_num();
    work(i);
}

A.16 Bloqueos

En el ejemplo siguiente, (para la sección 3.2), el argumento de las funciones de bloqueo debería tener el tipo omp_lock_t y no es necesario vaciarlo. Las funciones de bloqueo hacen que los subprocesos estén inactivos mientras esperan la entrada de la primera sección crítica, pero para realizar otro trabajo mientras esperan la entrada al segundo. La función omp_set_lock bloquea, pero la función omp_test_lock no, lo que permite que se trabaje en skip().

// omp_using_locks.c
// compile with: /openmp /c
#include <stdio.h>
#include <omp.h>

void work(int);
void skip(int);

int main() {
   omp_lock_t lck;
   int id;

   omp_init_lock(&lck);
   #pragma omp parallel shared(lck) private(id)
   {
      id = omp_get_thread_num();

      omp_set_lock(&lck);
      printf_s("My thread id is %d.\n", id);

      // only one thread at a time can execute this printf
      omp_unset_lock(&lck);

      while (! omp_test_lock(&lck)) {
         skip(id);   // we do not yet have the lock,
                     // so we must do something else
      }
      work(id);     // we now have the lock
                    // and can do the work
      omp_unset_lock(&lck);
   }
   omp_destroy_lock(&lck);
}

A.17 Bloqueos anidados

En el ejemplo siguiente, (para la sección 3.2), se demuestra cómo un bloqueo anidable se puede usar para sincronizar actualizaciones tanto en la estructura completa como en uno de sus miembros.

#include <omp.h>
typedef struct {int a,b; omp_nest_lock_t lck;} pair;

void incr_a(pair *p, int a)
{
    // Called only from incr_pair, no need to lock.
    p->a += a;
}

void incr_b(pair *p, int b)
{
    // Called both from incr_pair and elsewhere,
    // so need a nestable lock.

    omp_set_nest_lock(&p->lck);
    p->b += b;
    omp_unset_nest_lock(&p->lck);
}

void incr_pair(pair *p, int a, int b)
{
    omp_set_nest_lock(&p->lck);
    incr_a(p, a);
    incr_b(p, b);
    omp_unset_nest_lock(&p->lck);
}

void f(pair *p)
{
    extern int work1(), work2(), work3();
    #pragma omp parallel sections
    {
        #pragma omp section
            incr_pair(p, work1(), work2());
        #pragma omp section
            incr_b(p, work3());
    }
}

A.18 Anidadas para directivas

El ejemplo siguiente de fordirectiva de anidamiento es compatible porque las directivas for internas y externas se enlazan a regiones paralelas diferentes:

#pragma omp parallel default(shared)
{
    #pragma omp for
        for (i=0; i<n; i++)
        {
            #pragma omp parallel shared(i, n)
            {
                #pragma omp for
                    for (j=0; j<n; j++)
                        work(i, j);
            }
        }
}

También es compatible una variación siguiente del ejemplo anterior:

#pragma omp parallel default(shared)
{
    #pragma omp for
        for (i=0; i<n; i++)
            work1(i, n);
}

void work1(int i, int n)
{
    int j;
    #pragma omp parallel default(shared)
    {
        #pragma omp for
            for (j=0; j<n; j++)
                work2(i, j);
    }
    return;
}

A.19 Ejemplos donde se muestra un anidamiento incorrecto de directivas de uso compartido

Los ejemplos de esta sección ilustran las reglas de anidamiento de directivas.

El ejemplo siguiente no es compatible porque las directivas for internas y externas están anidadas y se enlazan a la misma directiva parallel:

void wrong1(int n)
{
  #pragma omp parallel default(shared)
  {
      int i, j;
      #pragma omp for
      for (i=0; i<n; i++) {
          #pragma omp for
              for (j=0; j<n; j++)
                 work(i, j);
     }
   }
}

La siguiente versión anidada dinámicamente del ejemplo anterior tampoco es compatible:

void wrong2(int n)
{
  #pragma omp parallel default(shared)
  {
    int i;
    #pragma omp for
      for (i=0; i<n; i++)
        work1(i, n);
  }
}

void work1(int i, int n)
{
  int j;
  #pragma omp for
    for (j=0; j<n; j++)
      work2(i, j);
}

El ejemplo siguiente no es compatible porque las directivas for y single están anidadas y se enlazan a la misma región paralela:

void wrong3(int n)
{
  #pragma omp parallel default(shared)
  {
    int i;
    #pragma omp for
      for (i=0; i<n; i++) {
        #pragma omp single
          work(i);
      }
  }
}

El ejemplo siguiente no es compatible porque una directiva barrier dentro de un for puede originar un interbloqueo:

void wrong4(int n)
{
  #pragma omp parallel default(shared)
  {
    int i;
    #pragma omp for
      for (i=0; i<n; i++) {
        work1(i);
        #pragma omp barrier
        work2(i);
      }
  }
}

El ejemplo siguiente no es compatible porque la barrier causa un interbloqueo, dado que solo un subproceso puede entrar en la sección crítica a la vez:

void wrong5()
{
  #pragma omp parallel
  {
    #pragma omp critical
    {
       work1();
       #pragma omp barrier
       work2();
    }
  }
}

El ejemplo siguiente no es compatible porque la barrier provoca un interbloqueo porque solo un subproceso ejecuta la sección single:

void wrong6()
{
  #pragma omp parallel
  {
    setup();
    #pragma omp single
    {
      work1();
      #pragma omp barrier
      work2();
    }
    finish();
  }
}

A.20 Directivas de barrera de enlace

Las reglas de enlace de directivas llaman a una directiva barrier para enlazar a la directiva parallel envolvente más cercana. Si desea más información sobre el enlace de directivas, consulte la sección 2.8.

En el ejemplo siguiente, la llamada de main a sub2 es compatible porque el barrier (en sub3) se enlaza a la región paralela en sub2. La llamada de main a sub1 es compatible, dado que la barrier se enlaza a la región paralela en la subrutina sub2. La llamada de main a sub3 es compatible porque barrier no se enlaza a una región paralela y se omite. Además, la barrier solo sincroniza el equipo de subprocesos en la región paralela envolvente y no todos los subprocesos creados en sub1.

int main()
{
    sub1(2);
    sub2(2);
    sub3(2);
}

void sub1(int n)
{
    int i;
    #pragma omp parallel private(i) shared(n)
    {
        #pragma omp for
        for (i=0; i<n; i++)
            sub2(i);
    }
}

void sub2(int k)
{
     #pragma omp parallel shared(k)
     sub3(k);
}

void sub3(int n)
{
    work(n);
    #pragma omp barrier
    work(n);
}

A.21 Variables de ámbito con la cláusula private

Los valores de i y j del siguiente ejemplo están sin definir al salir de la región paralela:

int i, j;
i = 1;
j = 2;
#pragma omp parallel private(i) firstprivate(j)
{
  i = 3;
  j = j + 2;
}
printf_s("%d %d\n", i, j);

Para obtener más información sobre la cláusula private, consulte la sección 2.7.2.1.

A.22 Cláusula (none) predeterminada

En el ejemplo siguiente se distinguen las variables que se ven afectadas por la cláusula default(none) y las que no:

// openmp_using_clausedefault.c
// compile with: /openmp
#include <stdio.h>
#include <omp.h>

int x, y, z[1000];
#pragma omp threadprivate(x)

void fun(int a) {
   const int c = 1;
   int i = 0;

   #pragma omp parallel default(none) private(a) shared(z)
   {
      int j = omp_get_num_thread();
             //O.K.  - j is declared within parallel region
      a = z[j];       // O.K.  - a is listed in private clause
                      //      - z is listed in shared clause
      x = c;          // O.K.  - x is threadprivate
                      //      - c has const-qualified type
      z[i] = y;       // C3052 error - cannot reference i or y here

      #pragma omp for firstprivate(y)
         for (i=0; i<10 ; i++) {
            z[i] = y;  // O.K. - i is the loop control variable
                       // - y is listed in firstprivate clause
          }
       z[i] = y;   // Error - cannot reference i or y here
   }
}

Para obtener más información sobre la cláusula default, consulte la sección 2.7.2.5.

A.23 Ejemplos de la directiva ordered

Es posible que haya muchas secciones ordenadas con for especificado con la cláusula ordered. En el primer ejemplo se presenta un caso no compatible porque la API especifica la regla siguiente:

«Una iteración de un bucle con una construcción for no debe ejecutar la misma directivaordered más de una vez y no debe ejecutar más de una directiva ordered.» (Consulte la sección 2.6.6.)

En este ejemplo de no compatibilidad, todas las iteraciones ejecutan dos secciones ordenadas:

#pragma omp for ordered
for (i=0; i<n; i++)
{
    ...
    #pragma omp ordered
    { ... }
    ...
    #pragma omp ordered
    { ... }
    ...
}

En el ejemplo siguiente de compatibilidad se muestra for con más de una sección ordenada:

#pragma omp for ordered
for (i=0; i<n; i++)
{
    ...
    if (i <= 10)
    {
        ...
        #pragma omp ordered
        { ... }
    }
    ...
    (i > 10)
    {
        ...
        #pragma omp ordered
        { ... }
    }
    ...
}

A. 24 Ejemplo de la cláusula private

La cláusula private de la región paralela solo está en vigor para la extensión léxica de la región, no para la extensión dinámica de la región. Por lo tanto, en el ejemplo siguiente, cualquier uso de la variable a dentro del bucle for en la rutina f hace referencia a una copia privada de a, mientras el uso en la rutina g se refiere a la global a.

int a;

void f(int n)
{
    a = 0;

    #pragma omp parallel for private(a)
    for (int i=1; i<n; i++)
    {
        a = i;
        g(i, n);
        d(a);     // Private copy of "a"
        ...
    }
    ...

void g(int k, int n)
{
    h(k,a); // The global "a", not the private "a" in f
}

A.25 Ejemplos de la cláusula de atributos de datos copyprivate

Ejemplo 1: la cláusula copyprivate puede usarse para difundir valores adquiridos por un único subproceso directamente a todas las instancias de las variables privadas de los demás subprocesos.

float x, y;
#pragma omp threadprivate(x, y)

void init( )
{
    float a;
    float b;

    #pragma omp single copyprivate(a,b,x,y)
    {
        get_values(a,b,x,y);
    }

    use_values(a, b, x, y);
}

Si se llama a la rutina init desde una región de serie, su comportamiento no se ve afectado por la presencia de las directivas. Después de que un subproceso llame a la rutina get_values por un subproceso, ningún subproceso deja la construcción hasta que los objetos privados designados por a, b, x e y en todos los subprocesos se han definido con los valores leídos.

Ejemplo 2: a diferencia del ejemplo anterior, supongamos que un subproceso determinado debe hacer la lectura, por ejemplo, el subproceso maestro. En este supuesto, la cláusula copyprivate no se puede usar para difundir directamente, pero puede emplearse para proporcionar acceso a un objeto compartido temporal.

float read_next( )
{
    float * tmp;
    float return_val;

    #pragma omp single copyprivate(tmp)
    {
        tmp = (float *) malloc(sizeof(float));
    }

    #pragma omp master
    {
        get_float( tmp );
    }

    #pragma omp barrier
    return_val = *tmp;
    #pragma omp barrier

    #pragma omp single
    {
       free(tmp);
    }

    return return_val;
}

Ejemplo 3: supongamos que no se puede determinar fácilmente el número de objetos de bloqueo requeridos dentro de la región paralela antes de escribirlo. La cláusula copyprivate puede usarse para dar acceso a objetos de bloqueo compartidos asignados dentro de la región paralela.

#include <omp.h>

omp_lock_t *new_lock()
{
    omp_lock_t *lock_ptr;

    #pragma omp single copyprivate(lock_ptr)
    {
        lock_ptr = (omp_lock_t *) malloc(sizeof(omp_lock_t));
        omp_init_lock( lock_ptr );
    }

    return lock_ptr;
}

A. 26 La directiva threadprivate

En los ejemplos siguientes se ilustra cómo se usa la directiva threadprivate para dar a cada subproceso un contador independiente.

Ejemplo 1

int counter = 0;
#pragma omp threadprivate(counter)

int sub()
{
    counter++;
    return(counter);
}

Ejemplo 2

int sub()
{
    static int counter = 0;
    #pragma omp threadprivate(counter)
    counter++;
    return(counter);
}

A.27 Matrices de longitud variable C99

En el ejemplo siguiente se explica cómo se usan matrices de longitud variable (VLA) de C99 en una directiva firstprivate.

Nota:

Actualmente las matrices de longitud variable no se admiten en Visual C++.

void f(int m, int C[m][m])
{
    double v1[m];
    ...
    #pragma omp parallel firstprivate(C, v1)
    ...
}

A.28 Cláusula num_threads

En el ejemplo siguiente muestra la cláusula de num_threats. La región paralela se ejecuta con un máximo de diez subprocesos.

#include <omp.h>
int main()
{
    omp_set_dynamic(1);
    ...
    #pragma omp parallel num_threads(10)
    {
        ... parallel region ...
    }
}

A.29 Construcciones de uso compartido en una construcción crítica

En el ejemplo siguiente se muestra cómo se usa un construcción de uso compartido en una construcción critical. Este ejemplo es compatible puesto que la construcción de uso compartido y la critical construcción no se enlazan a la misma región paralela.

void f()
{
  int i = 1;
  #pragma omp parallel sections
  {
    #pragma omp section
    {
      #pragma omp critical (name)
      {
        #pragma omp parallel
        {
          #pragma omp single
          {
            i++;
          }
        }
      }
    }
  }
}

A.30 Reprivatización

En el ejemplo siguiente se muestra la reprivatización de variables. Las variables privadas se pueden volver a marcar private en una directiva anidada. No se necesita compartir esas variables en la región paralela envolvente.

int i, a;
...
#pragma omp parallel private(a)
{
  ...
  #pragma omp parallel for private(a)
  for (i=0; i<10; i++)
     {
       ...
     }
}

A.31 Funciones de bloqueo seguras para subprocesos

En el ejemplo de C++ siguiente se explica cómo inicializar una matriz de bloqueos en una región paralela mediante omp_init_lock.

// A_13_omp_init_lock.cpp
// compile with: /openmp
#include <omp.h>

omp_lock_t *new_locks() {
   int i;
   omp_lock_t *lock = new omp_lock_t[1000];
   #pragma omp parallel for private(i)
   for (i = 0 ; i < 1000 ; i++)
      omp_init_lock(&lock[i]);

   return lock;
}

int main () {}