Partager via


A. Exemples

Vous trouverez ci-dessous des exemples de constructions définies dans ce document. Une instruction suivant une directive est composée uniquement si nécessaire, et une instruction non composée est mise en retrait par rapport à une directive qui précède.

A.1 Boucle simple en parallèle

L’exemple suivant montre comment paralléliser une boucle à l’aide de la directive parallel for. La variable d’itération de boucle est privée par défaut. Il n’est donc pas nécessaire de la spécifier explicitement dans une clause privée.

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

A.2 Compilation conditionnelle

Les exemples suivants illustrent l’utilisation de la compilation conditionnelle à l’aide de la macro OpenMP _OPENMP. Avec la compilation OpenMP, la macro _OPENMP devient définie.

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

L’opérateur de préprocesseur défini permet à plusieurs macros d’être testées dans une directive unique.

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

A.3 Régions parallèles

La directive parallel peut être utilisée dans les programmes parallèles à granularité élevée. Dans l’exemple suivant, chaque thread de la région parallèle détermine sur quelle partie du tableau global x travailler, en fonction du numéro de 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 Clause nowait

S’il existe de nombreuses boucles indépendantes dans une région parallèle, vous pouvez utiliser la clause nowait pour éviter la barrière implicite à la fin de la directive for, comme suit :

#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 Directive critical

L’exemple suivant inclut plusieurs directives critical. L’exemple illustre un modèle de mise en file d’attente dans lequel une tâche est enlevée de la file d’attente et sur laquelle des opérations sont effectuées. Pour éviter que de nombreux threads n’enlève la même tâche de la file d’attente, l’opération d’enlèvement de la file d’attente doit se trouver dans une section critical. Étant donné que les deux files d’attente de cet exemple sont indépendantes, elles sont protégées par des directives critical portant différents noms, xaxis et 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 Clause lastprivate

L’exécution correcte dépend parfois de la valeur que la dernière itération d’une boucle affecte à une variable. Ces programmes doivent répertorier tous ces types de variables en tant qu’arguments d’une clause lastprivate, afin que les valeurs des variables soient les mêmes que lorsque la boucle est exécutée séquentiellement.

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

Dans l’exemple précédent, la valeur de i à la fin de la région parallèle est égale à n-1, comme dans le cas séquentiel.

A.7 Clause reduction

L’exemple suivant illustre la clause 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 Sections parallèles

Dans l’exemple suivant (pour la section 2.4.2), les fonctions xaxis, yaxis et zaxis peuvent être exécutées simultanément. La première directive section est facultative. Toutes les directives section doivent apparaître dans l’étendue lexicale de la construction parallel sections.

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

A.9 Directives single

L’exemple suivant illustre la directive single. Dans l’exemple, un seul thread (généralement le premier thread qui rencontre la directive single) imprime le message de progression. L’utilisateur ne doit pas effectuer d’hypothèse quant au thread qui exécutera la section single. Tous les autres threads ignoreront la section single et s’arrêteront à la barrière à la fin de la construction single. Si d’autres threads peuvent continuer sans attendre que le thread exécute la section single, une clause nowait peut être spécifiée sur la directive 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 Ordre séquentiel

Les sections ordonnées sont utiles pour classer séquentiellement la sortie d’un travail effectué en parallèle. Le programme suivant imprime les index dans l’ordre séquentiel :

#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 Nombre fixe de threads

Certains programmes dépendent d’un nombre fixe et préspecifié de threads pour s’exécuter correctement. Le paramètre par défaut pour l’ajustement dynamique du nombre de threads étant défini par l’implémentation, ces programmes peuvent choisir de désactiver la fonctionnalité de threads dynamiques et de définir explicitement le nombre de threads pour conserver la portabilité. L’exemple suivant montre comment procéder à l’aide de omp_set_dynamic et 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);
}

Dans cet exemple, le programme s’exécute correctement uniquement s’il est exécuté par 16 threads. Si l’implémentation n’est pas capable de prendre en charge 16 threads, le comportement de cet exemple est défini par l’implémentation.

Le nombre de threads exécutant une région parallèle reste constant pendant une région parallèle, quel que soit le paramètre des threads dynamiques. Le mécanisme de threads dynamiques détermine le nombre de threads à utiliser au début de la région parallèle, et le maintient constant pendant toute la durée de la région.

A.12 Directive atomic

L’exemple suivant évite les conditions de concurrence (mises à jour simultanées d’un élément de x par de nombreux threads) en faisant appel à la directive 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);
    }

L’avantage de l’utilisation de la directive atomic dans cet exemple est qu’elle permet de mettre à jour deux éléments différents de x en parallèle. Si une directive critical est utilisée à la place, toutes les mises à jour apportées aux éléments de x sont exécutées en série (mais pas dans un ordre garanti).

La directive atomic s’applique uniquement à l’instruction C ou C++ qui la suit immédiatement. Par conséquent, les éléments de y ne sont pas mis à jour atomiquement dans cet exemple.

A.13 Directive flush avec une liste

L’exemple suivant utilise la directive flush pour la synchronisation point à point d’objets spécifiques entre des paires 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 Directive flush sans liste

L’exemple suivant (pour la section 2.6.5) distingue les objets partagés affectés par une directive flush sans liste des objets partagés qui ne sont pas affectés :

// 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 Nombre de threads utilisés

Prenons l’exemple incorrect suivant (pour la section 3.1.2) :

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

L’appel omp_get_num_threads() retourne 1 dans la section série du code. Par conséquent, np sera toujours égal à 1 dans l’exemple précédent. Pour déterminer le nombre de threads qui seront déployés pour la région parallèle, l’appel doit se trouver à l’intérieur de la région parallèle.

L’exemple suivant montre comment réécrire ce programme sans inclure de requête pour le nombre de threads :

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

A.16 Verrous

Dans l’exemple suivant (pour la section 3.2), l’argument des fonctions de verrouillage doit être de type omp_lock_t, et il n’est pas nécessaire de le vider. Les fonctions de verrouillage font en sorte que les threads soient inactifs en attendant l’entrée dans la première section critique, mais qu’ils effectuent d’autres tâches en attendant l’entrée dans la deuxième. La fonction omp_set_lock bloque, mais pas la fonction omp_test_lock, ce qui permet d’effectuer le travail dans 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 Verrous pouvant être imbriqués

L’exemple suivant (pour la section 3.2) montre comment un verrou pouvant être imbriqué peut être utilisé pour synchroniser les mises à jour à la fois d’une structure entière et de l’un de ses membres.

#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 Directives for imbriquées

L’exemple suivant d’imbrication de directives for est conforme, car les directives for internes et externes sont liées à différentes régions parallèles :

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

La variante suivante de l’exemple précédent est également conforme :

#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 Exemples illustrant l’imbrication incorrecte de directives de partage de travail

Les exemples de cette section illustrent les règles d’imbrication de directives.

L’exemple suivant n’est pas conforme, car les directives for internes et externes sont imbriquées et liées à la même directive 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 version imbriquée dynamiquement suivante de l’exemple précédent est également non conforme :

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

L’exemple suivant n’est pas conforme, car les directives for et single sont imbriquées et liées à la même région parallèle :

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

L’exemple suivant n’est pas conforme, car une directive barrier à l’intérieur d’une directive for peut entraîner un blocage :

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

L’exemple suivant n’est pas conforme, car la directive barrier génère un blocage en raison du fait qu’un seul thread à la fois peut entrer dans la section critique :

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

L’exemple suivant n’est pas conforme, car la directive barrier génère un blocage en raison du fait qu’un seul thread exécute la section single :

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

A.20 Lier des directives barrier

Les règles de liaison de directives imposent le recours à une directive barrier pour établir une liaison à la directive parallel englobante la plus proche. Pour plus d’informations sur la liaison de directives, consultez la section 2.8.

Dans l’exemple suivant, l’appel à sub2 à partir de main est conforme, car la directive barrier (dans sub3) est liée à la région parallèle dans sub2. L’appel à sub1 à partir de main est conforme, car la directive barrier est liée à la région parallèle dans la sous-routine sub2. L’appel à sub3 à partir de main est conforme, car la directive barrier n’est liée à aucune région parallèle et est ignorée. En outre, la directive barrier synchronise uniquement l’équipe de threads dans la région parallèle englobante, et non tous les threads créés dans 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 Définir l’étendue des variables avec la clause private

Les valeurs de i et j dans l’exemple suivant ne sont pas définies à la sortie de la région parallèle :

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

Pour plus d’informations sur la clause private, consultez la section 2.7.2.1.

A.22 Clause default(none)

L’exemple suivant distingue les variables affectées par la clause default(none) des variables qui ne le sont pas :

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

Pour plus d’informations sur la clause default, consultez la section 2.7.2.5.

A.23 Exemples de directive ordered

Il est possible d’avoir de nombreuses sections ordonnées avec une directive for spécifiée avec la clause ordered. Le premier exemple est non conforme, car l’API spécifie la règle suivante :

« Une itération d’une boucle avec une construction for ne doit pas exécuter la même directive ordered plus d’une fois, et elle ne doit pas exécuter plus d’une directive ordered. » (Voir la section 2.6.6.)

Dans cet exemple non conforme, toutes les itérations exécutent deux sections ordonnées :

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

L’exemple conforme suivant montre une directive for avec plusieurs sections ordonnées :

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

A.24 Exemple de la clause private

La clause private d’une région parallèle est uniquement en vigueur pour l’étendue lexicale de la région, et non pour l’étendue dynamique de la région. Par conséquent, dans l’exemple qui suit, toute utilisation de la variable a dans la boucle for de la routine f fait référence à une copie privée de a, tandis qu’une utilisation dans la routine g fait référence à la variable a globale.

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 Exemples de la clause d’attribut de données copyprivate

Exemple 1 : la clause copyprivate peut être utilisée pour diffuser des valeurs acquises par un seul thread directement à toutes les instances des variables privées dans les autres 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);
}

Si la routine init est appelée à partir d’une région série, son comportement n’est pas affecté par la présence des directives. Une fois que l’appel à la routine get_values a été exécuté par un thread, aucun thread ne quitte la construction tant que les objets privés désignés par a, b, x et y dans tous les threads n’ont pas été définis avec les valeurs lues.

Exemple 2 : par opposition à l’exemple précédent, supposez que la lecture doit être effectuée par un thread particulier, par exemple le thread principal. Dans ce cas, la clause copyprivate ne peut pas être utilisée pour effectuer la diffusion directement, mais elle peut être utilisée pour fournir l’accès à un objet partagé temporaire.

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

Exemple 3 : supposez que le nombre d’objets de verrou requis dans une région parallèle ne peut pas être facilement déterminé avant l’entrée dans cette région. La clause copyprivate peut être utilisée pour fournir l’accès aux objets de verrou partagés alloués dans cette région parallèle.

#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 Directive threadprivate

Les exemples suivants montrent comment utiliser la directive threadprivate pour donner à chaque thread un compteur distinct.

Exemple 1

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

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

Exemple 2

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

A.27 Utilisation de tableaux de longueur variable C99

L’exemple suivant montre comment utiliser des tableaux de longueur variable C99 dans une directive firstprivate.

Remarque

Les tableaux de longueur variable ne sont actuellement pas pris en charge en Visual C++.

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

A.28 Clause num_threads

L’exemple suivant illustre la clause num_threads. La région parallèle est exécutée avec un maximum de 10 threads.

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

A.29 Constructions de partage de travail à l’intérieur d’une construction critical

L’exemple suivant illustre l’utilisation d’une construction de partage de travail à l’intérieur d’une construction critical. Cet exemple est conforme, car la construction de partage de travail et la construction critical ne sont pas liées à la même région parallèle.

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 Reprivatisation

L’exemple suivant illustre la reprivatisation de variables. Les variables privées peuvent être marquées private à nouveau dans une directive imbriquée. Vous n’avez pas besoin de partager ces variables dans la région parallèle englobante.

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

A.31 Fonctions de verrouillage thread-safe

L’exemple C++ suivant montre comment initialiser un tableau de verrous dans une région parallèle à l’aide de 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 () {}