Partilhar via


Um. Exemplos

Seguem-se exemplos das construções definidas neste documento. Uma declaração que segue uma diretiva é composta apenas quando necessário, e uma declaração não composta é recuada em relação a uma diretiva que a precede.

A.1 Um loop simples em paralelo

O exemplo a seguir demonstra como paralelizar um loop usando a diretiva parallel for . A variável de iteração de loop é privada por padrão, portanto, não é necessário especificá-la explicitamente em uma cláusula privada.

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

A.2 Compilação condicional

Os exemplos a seguir ilustram o uso da compilação condicional usando o _OPENMP de macro OpenMP. Com a compilação OpenMP, a _OPENMP macro torna-se definida.

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

O operador de pré-processador definido permite que mais de uma macro seja testada em uma única diretiva.

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

A.3 Regiões paralelas

A diretiva paralela pode ser usada em programas paralelos de grão grosso. No exemplo a seguir, cada thread na região paralela decide em qual parte da matriz x global trabalhar, com base no número do thread:

#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 A cláusula nowait

Se houver muitos loops independentes dentro de uma região paralela, você pode usar a cláusula nowait para evitar a barreira implícita no final da for diretiva, da seguinte maneira:

#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 A diretiva crítica

O exemplo a seguir inclui várias diretivas críticas . O exemplo ilustra um modelo de fila no qual uma tarefa é retirada e processada. Para se proteger contra muitos threads que enfileiram a mesma tarefa, a operação de enfileiramento deve estar em uma critical seção. Como as duas filas neste exemplo são independentes, elas são protegidas por critical diretivas com nomes diferentes, xaxis e 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 A última cláusula privada

A execução correta às vezes depende do valor que a última iteração de um loop atribui a uma variável. Tais programas devem listar todas essas variáveis como argumentos para uma cláusula lastprivate para que os valores das variáveis sejam os mesmos de quando o loop é executado sequencialmente.

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

No exemplo anterior, o valor de i no final da região paralela será igual n-1a , como no caso sequencial.

A.7 A cláusula de redução

O exemplo a seguir demonstra a cláusula de redução :

#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 Secções paralelas

No exemplo a seguir (para a seção 2.4.2), as funções xaxis, yaxis e zaxis podem ser executadas simultaneamente. A primeira section diretiva é facultativa. Todas as section diretivas precisam aparecer na extensão lexical do parallel sections constructo.

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

A.9 Diretivas únicas

O exemplo a seguir demonstra a diretiva única . No exemplo, apenas um thread (geralmente o primeiro thread que encontra a single diretiva) imprime a mensagem de progresso. O usuário não deve fazer nenhuma suposição sobre qual thread executará a single seção. Todos os outros threads irão pular a seção single e parar na barreira ao fim da estrutura single. Se outros segmentos puderem prosseguir sem esperar que o segmento execute a single seção, uma cláusula nowait pode ser especificada na diretiva 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 Ordenação sequencial

As seções ordenadas são úteis para ordenar sequencialmente a saída do trabalho feito em paralelo. O seguinte programa imprime os índices em ordem sequencial:

#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 Um número fixo de roscas

Alguns programas dependem de um número fixo e pré-especificado de threads para serem executados corretamente. Como a configuração padrão para o ajuste dinâmico do número de threads é definida pela implementação, esses programas podem optar por desativar o recurso de threads dinâmicos e definir o número de threads explicitamente para manter a portabilidade. O exemplo a seguir mostra como fazer isso usando omp_set_dynamic e 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);
}

Neste exemplo, o programa é executado corretamente somente se for executado por 16 threads. Se a implementação não for capaz de suportar 16 threads, o comportamento deste exemplo será definido pela implementação.

O número de threads que executam uma região paralela permanece constante durante uma região paralela, independentemente da configuração de threads dinâmicos. O mecanismo de threads dinâmicos determina o número de threads a utilizar no início da região paralela e mantém-no constante ao longo da região.

A.12 A diretiva atómica

O seguinte exemplo evita condições de concorrência (atualizações simultâneas de um elemento de x por várias threads) utilizando a diretiva 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);
    }

A vantagem de usar a atomic diretiva neste exemplo é que ela permite que atualizações de dois elementos diferentes de x ocorram em paralelo. Se uma diretiva crítica for usada em vez disso, todas as atualizações dos elementos de x serão executadas em série (embora não em qualquer ordem garantida).

A diretiva atomic aplica-se apenas à instrução C ou C++ imediatamente a seguir. Como resultado, os elementos de y não são atualizados atomicamente neste exemplo.

A.13 Uma diretiva "flush" com uma lista

O exemplo a seguir usa a diretiva flush para sincronização ponto a ponto de objetos específicos entre pares de threads.

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 Uma diretiva de descarga sem lista

O exemplo a seguir (para a seção 2.6.5) distingue os objetos compartilhados afetados por uma flush diretiva sem lista dos objetos compartilhados que não são afetados:

// 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 O número de roscas utilizadas

Considere o seguinte exemplo incorreto (para o ponto 3.1.2):

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

A omp_get_num_threads() chamada retorna 1 na seção serial do código, portanto, np sempre será igual a 1 no exemplo anterior. Para determinar o número de threads que serão alocados para a região paralela, a chamada deve estar inserida dentro dessa região paralela.

O exemplo a seguir mostra como reescrever este programa sem incluir uma consulta para o número de threads:

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

A.16 Fechaduras

No exemplo a seguir (para a seção 3.2), o argumento para as funções de bloqueio deve ser do tipo omp_lock_t, sendo que não há necessidade de o liberar. As funções de bloqueio fazem com que o thread fique ocioso enquanto aguarda a entrada na primeira seção crítica, mas que realize outro tipo de trabalho enquanto aguarda a entrada na segunda seção. A função omp_set_lock bloqueia, mas a função omp_test_lock não, permitindo que o trabalho em skip() seja realizado.

// 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 Fechos neestáveis

O exemplo a seguir (para a seção 3.2) demonstra como um bloqueio aninhado pode ser usado para sincronizar atualizações tanto para toda a estrutura quanto para um dos seus membros.

#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 Aninhado para diretivas

O seguinte exemplo de for aninhamento de diretivas é compatível porque as diretivas internas e externas for se ligam a diferentes regiões paralelas:

#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);
            }
        }
}

Uma variação a seguir do exemplo anterior também é compatível:

#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 Exemplos que mostram aninhamento incorreto de diretivas de partilha de trabalho

Os exemplos nesta secção ilustram as regras de aninhamento de diretivas.

O exemplo a seguir não é compatível porque as diretivas for interna e externa são aninhadas e se ligam à mesma diretiva 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);
     }
   }
}

A seguinte versão aninhada dinamicamente do exemplo anterior também não é compatível:

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);
}

O exemplo a seguir não é conforme porque as diretivas for e single são aninhadas e se ligam à mesma região 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);
      }
  }
}

O exemplo a seguir não é compatível porque uma barrier diretiva dentro de um for pode resultar em impasse:

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);
      }
  }
}

O exemplo a seguir não é compatível porque resulta barrier em deadlock devido ao fato de que apenas um thread de cada vez pode entrar na seção crítica:

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

O exemplo a seguir não está em conformidade porque barrier resulta em deadlock porque apenas uma linha de execução executa a seção single.

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

A.20 Diretivas vinculativas de barreira

As regras vinculativas da diretiva exigem que uma barrier diretiva vincule a diretiva anexa parallel mais próxima. Para mais informações sobre a vinculação de diretivas, ver secção 2.8.

No exemplo a seguir, a chamada de main para sub2 é compatível porque o barrier (em sub3) se liga à região paralela em sub2. A chamada de main para sub1 é compatível porque o barrier se liga à região paralela na subrotina sub2. A chamada de main para sub3 é compatível porque barrier não se liga a nenhuma região paralela e é ignorado. Além disso, o barrier sincroniza apenas o conjunto de threads na região paralela envolvente e não todos os threads criados em 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 Variáveis de âmbito com a cláusula privada

Os valores de i e j no exemplo a seguir são indefinidos na saída da região 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 mais informações sobre a private cláusula, ver secção 2.7.2.1.

A.22 A cláusula default(none)

O exemplo a seguir distingue as variáveis que são afetadas pela default(none) cláusula das variáveis que não são:

// 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 mais informações sobre a default cláusula, ver secção 2.7.2.5.

A.23 Exemplos da diretiva ordenada

É possível ter muitas seções ordenadas com um for especificado com a ordered cláusula. O primeiro exemplo é incompatível porque a API especifica a seguinte regra:

"Uma iteração de um loop com uma for construção não deve executar a mesma ordered diretiva mais de uma vez e não deve executar mais de uma ordered diretiva." (Ver secção 2.6.6.)

Neste exemplo não compatível, todas as iterações executam duas seções ordenadas:

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

O exemplo compatível a seguir mostra uma for com mais de uma seção ordenada:

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

A.24 Exemplo de cláusula privada

A cláusula privada de uma região paralela só está em vigor para a extensão lexical da região, não para a extensão dinâmica da região. Portanto, no exemplo a seguir, qualquer uso da variável a dentro do for loop na rotina f refere-se a uma cópia privada de a, enquanto um uso na rotina g refere-se ao 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 Exemplos da cláusula de atributo copyprivate data

Exemplo 1: A cláusula copyprivate pode ser usada para transmitir valores adquiridos por um único thread diretamente para todas as instâncias das variáveis privadas nos outros threads.

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);
}

Se a rotina init for chamada de uma região serial, o comportamento não será afetado pela presença das diretivas. Depois que a chamada para a rotina get_values tiver sido executada por um thread, nenhum thread deixará a construção até que os objetos privados designados por a, b, x e y em todos os threads tenham sido definidos com os valores lidos.

Exemplo 2: Em contraste com o exemplo anterior, suponha que a leitura deve ser executada por um thread específico, digamos o thread mestre. Nesse caso, a copyprivate cláusula não pode ser usada para fazer a transmissão diretamente, mas pode ser usada para fornecer acesso a um objeto compartilhado temporário.

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;
}

Exemplo 3: Suponha que o número de objetos de bloqueio necessários dentro de uma região paralela não possa ser facilmente determinado antes de inseri-la. A copyprivate cláusula pode ser usada para fornecer acesso a objetos de bloqueio compartilhados alocados dentro dessa região 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 A diretiva threadprivate

Os exemplos a seguir demonstram como usar a diretiva threadprivate para dar a cada thread um contador separado.

Exemplo 1

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

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

Exemplo 2

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

A.27 Matrizes de comprimento variável C99

O exemplo a seguir demonstra como usar C99 Variable Length Arrays (VLAs) em uma diretiva firstprivate .

Observação

Matrizes de comprimento variável não são suportadas atualmente no Visual C++.

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

A.28 A cláusula num_threads

O exemplo a seguir demonstra a cláusula num_threads . A região paralela é executada com um máximo de 10 threads.

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

A.29 Construções de partilha de tarefas dentro de uma construção crítica

O exemplo a seguir demonstra o uso de uma construção de compartilhamento de trabalho dentro de uma critical construção. Este exemplo é compatível porque a construção de compartilhamento de trabalho e a critical construção não se ligam à mesma região 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 Reprivatização

O exemplo a seguir demonstra a reprivatização de variáveis. As variáveis privadas podem ser marcadas private novamente numa diretiva aninhada. Você não precisa compartilhar essas variáveis na região paralela que o inclui.

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

A.31 Funções de bloqueio seguras para threads

O exemplo C++ a seguir demonstra como inicializar uma matriz de bloqueios em uma região paralela usando 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 () {}