Partilhar via


R. Exemplos

Veja os exemplos a seguir dos constructos definidos neste documento. Uma instrução depois de uma diretiva é composta somente quando necessário, e uma instrução não composta é recuada de uma diretiva anterior a ela.

A.1 Um loop simples em paralelo

O exemplo a seguir demonstra como paralelizar um loop usando o paralelo para diretiva. 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 a macro OpenMP _OPENMP. Com a compilação OpenMP, a macro _OPENMP se torna 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 alta granularidade. No exemplo a seguir, cada thread na região paralela decide em qual parte da matriz global x 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 em uma região paralela, você poderá usar a cláusula nowait para evitar a barreira implícita no final da diretiva for 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 enfileiramento no qual uma tarefa é removida da lista e trabalhada. Para se proteger contra muitos threads que removem da fila a mesma tarefa, a operação de remoção da fila deve estar em uma seção critical. Como as duas filas neste exemplo são independentes, elas são protegidas por diretivas critical 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 cláusula lastprivate

A execução correta às vezes depende do valor que a última iteração de um loop atribui a uma variável. Esses 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 em sequência.

#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 a n-1, como no caso sequencial.

A.7 A cláusula reduction

O exemplo a seguir demonstra a cláusula reduction:

#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 Seçõ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 diretiva section é opcional. Todas as diretivas section precisam aparecer na extensão lexical do constructo parallel sections.

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

A.9 Diretivas single

O exemplo a seguir demonstra o uso da diretiva single. No exemplo, apenas um thread (geralmente o primeiro que encontra a diretiva single) imprime a mensagem de progresso. O usuário não deve fazer nenhuma suposição sobre qual thread executará a seção single. Todos os outros threads ignorarão a seção single e pararão na barreira no final do constructo single. Se outros threads puderem continuar sem aguardar o thread executando a seção single, uma cláusula nowait poderá 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 que é feito em paralelo. O programa a seguir 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 threads

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 a capacidade de threads dinâmicos e definir o número de threads explicitamente para manter a portabilidade. O exemplo a seguir mostrar 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 será executado corretamente somente se for executado por 16 threads. Se a implementação não puder dar suporte a 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 dos threads dinâmicos. O mecanismo de threads dinâmicos determina o número de threads a serem usados no início da região paralela e o mantém constante pela duração da região.

A.12 A diretiva atomic

O exemplo a seguir evita condições de corrida (atualizações simultâneas de um elemento de x por muitos threads) usando a diretiva atomic :

#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 diretiva atomic neste exemplo é que ela permite que atualizações de dois elementos diferentes de x ocorram em paralelo. Se, em vez disso, for usada uma diretiva crítica, todas as atualizações de elementos de x serão executadas serialmente (embora não em qualquer ordem garantida).

A diretiva atomic se aplica somente à instrução C ou C++ imediatamente após ela. 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 flush sem uma lista

O exemplo a seguir (para a seção 2.6.5) distingue os objetos compartilhados afetados por uma diretiva flush 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 threads usados

Considere o exemplo incorreto a seguir (para a seção 3.1.2):

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

A chamada omp_get_num_threads() 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 implantados na região paralela, a chamada deve estar dentro da região paralela.

O exemplo a seguir mostra como regenerar 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 Bloqueios

No exemplo a seguir (para a seção 3.2), o argumento para as funções de bloqueio deve ter tipo omp_lock_t e não há necessidade de liberá-lo. As funções de bloqueio fazem com que os threads fiquem ociosos enquanto aguardam a entrada na primeira seção crítica, mas fazem outro trabalho enquanto aguardam a entrada na segunda. A função omp_set_lock bloqueia, mas a função omp_test_lock não, o que permite que o trabalho em skip() seja feito.

// 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 Bloqueios aninháveis

O exemplo a seguir (para a seção 3.2) demonstra como um bloqueio aninhável pode ser usado para sincronizar as atualizações para uma estrutura inteira e para um de 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 exemplo a seguir de for directive nesting está em conformidade porque as diretivas for internas e externas se vinculam 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 está em conformidade:

#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 o aninhamento incorreto de diretivas de compartilhamento do trabalho

Os exemplos nesta seção ilustram as regras de aninhamento de diretiva.

O exemplo a seguir não está em conformidade porque as diretivas internas e externas for estão aninhadas e vinculadas à 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 está em conformidade:

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 está em conformidade porque as diretivas for e single estão aninhadas e vinculadas à 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 está em conformidade porque uma diretiva barrier dentro de um for pode resultar em deadlock:

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 está em conformidade devido aos resultados barrier no deadlock, já que apenas um thread por vez pode inserir a 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 devido aos resultados barrier no deadlock, já que apenas um thread executa a seção single:

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

A.20 Associar diretivas barrier

As regras de associação de diretiva exigem que uma diretiva barrier seja associada à diretiva parallel delimitadora mais próxima. Para saber mais sobre a associação de diretivas, consulte a seção 2.8.

No exemplo a seguir, a chamada de main para sub2 está em conformidade porque a barrier (na sub3) se associa à região paralela na sub2. A chamada de main para sub1 está em conformidade porque a barrier se associa à região paralela na sub-rotina sub2. A chamada de main para sub3 está em conformidade porque a barrier não se associa a nenhuma região paralela e é ignorada. Além disso, a barrier somente sincroniza a equipe de threads na região paralela delimitadora e não todos os threads criados na 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 escopo com a cláusula private

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 saber mais sobre a cláusula private, consulte a seção 2.7.2.1.

A.22 A cláusula default(none)

O exemplo a seguir distingue as variáveis afetadas pela cláusula default(none) 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 saber mais sobre a cláusula default, consulte a seção 2.7.2.5.

A.23 Exemplos da diretiva ordered

É possível ter muitas seções ordenadas com uma for especificada com a cláusula ordered. O primeiro exemplo não está em conformidade porque a API especifica a seguinte regra:

“Uma iteração de um loop com um constructo for não deve executar a mesma diretiva ordered mais de uma vez e não deve executar mais de uma diretiva ordered.” (Consulte a seção 2.6.6.)

Neste exemplo não conforme, 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 em conformidade 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 da cláusula private

A cláusula private de uma região paralela só está em vigor para a extensão lexical da região, não para sua extensão dinâmica. Portanto, no exemplo a seguir, qualquer uso da variável a dentro do loop for na rotina f se refere a uma cópia private de a, enquanto um uso na rotina g se refere ao a global.

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 de dados copyprivate

Exemplo 1: A cláusula copyprivate pode ser usada para difundir 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, seu 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 sairá do constructo 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 deva ser executada por um thread específico, digamos, o thread mestre. Nesse caso, a cláusula copyprivate 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 em uma região paralela não possa ser determinado facilmente antes de inseri-lo. A cláusula copyprivate pode ser usada para fornecer acesso a objetos de bloqueio compartilhados alocados nessa 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 VLAs (Matrizes de comprimento variável) C99 em uma diretiva firstprivate.

Observação

No momento, não há suporte para matrizes de comprimento variável 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>
main()
{
    omp_set_dynamic(1);
    ...
    #pragma omp parallel num_threads(10)
    {
        ... parallel region ...
    }
}

A.29 Constructos de compartilhamento de trabalho em um constructo crítico

O exemplo a seguir demonstra o uso de um constructo de compartilhamento de trabalho dentro de um constructo critical. Este exemplo está em conformidade porque o constructo de compartilhamento de trabalho e o constructo critical não se associam à 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. Variáveis privadas podem ser marcadas como private novamente em uma diretiva aninhada. Você não precisa compartilhar as variáveis na região paralela delimitadora.

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 thread-safe

O exemplo de 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 () {}