A. 示例

下面是本文档中定义的构造的示例。 仅当必要时,指令后面的语句才是复合语句,并且非复合语句会从它前面的指令缩进。

A.1 并行简单循环

以下示例演示如何使用 parallel for 指令并行化循环。 循环迭代变量默认为私有变量,因此无需在 private 子句中显式指定它。

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

A.2 条件编译

以下示例演示如何通过 OpenMP 宏 _OPENMP 来使用条件编译。 借助 OpenMP 编译,_OPENMP 宏会成为已定义状态。

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

通过已定义的预处理器运算符可以在单个指令中测试多个宏。

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

A.3 并行区域

parallel 指令可在粗粒度并行程序中使用。 在以下示例中,并行区域中的每个线程都基于线程编号决定要处理全局数组 x 的哪个部分:

#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 nowait 子句

如果并行区域中存在许多独立循环,则可以使用 nowait 子句避免 for 指令末尾存在隐式屏障,如下所示:

#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 critical 指令

以下示例包含多个 critical 指令。 该示例演示了一个队列模型,任务会在其中取消排队并进行处理。 若要防止多个线程对相同任务取消排队,取消排队操作必须处于 critical 节中。 由于此示例中的两个队列是独立的,因此它们受具有不同名称(xaxis 和 yaxis)的 critical 指令保护

#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 lastprivate 子句

正确执行有时取决于循环的最后一次迭代分配给变量的值。 此类程序必须将所有此类变量列为 lastprivate 子句的参数,以便变量的值与按顺序执行循环时相同。

#pragma omp parallel
{
   #pragma omp for lastprivate(i)
      for (i=0; i<n-1; i++)
         a[i] = b[i] + b[i+1];
}
a[i]=b[i];

在前面的示例中,并行区域末尾的 i 值会等于 n-1(与顺序情况一样)。

A.7 reduction 子句

以下示例演示 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 并行节

在以下示例中(适用于第 2.4.2 节),函数 xaxis、yaxis 和 zaxis 可以并发执行。 第一个 section 指令是可选的。 所有 section 指令都需要出现在 parallel sections 构造的词法盘区中。

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

A.9 Single 指令

下面的示例演示 single 指令。 在该示例中,只有一个线程(通常是遇到 single 指令的第一个线程)会打印进度消息。 用户不得对哪个线程将执行 single 节进行任何假设。 所有其他线程都会跳过 single 节,并在 single 构造末尾的屏障处停止。 如果其他线程可以继续,而无需等待执行 single 节的线程,则可以在 single 指令中指定 nowait 子句。

#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 顺序排序

有序节用于按顺序对并行完成的工作的输出进行排序。 以下程序按顺序打印出索引:

#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 固定数量的线程

某些程序依靠预先指定的固定数量线程来正确执行。 因为线程数动态调整的默认设置是实现定义的,所有此类程序可以选择关闭动态线程功能,并显式设置线程数以保持可移植性。 下面的示例演示如何使用 omp_set_dynamicomp_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);
}

在此示例中,程序仅当由 16 个线程执行时才会正确执行。 如果实现无法支持 16 个线程,则此示例的行为是实现定义的。

无论动态线程设置如何,执行并行区域的线程数都会在并行区域期间保持恒定。 动态线程机制在并行区域开头确定要使用的线程数,并使它在区域持续时间内保持恒定。

A.12 atomic 指令

以下示例使用 atomic 指令避免争用条件(许多线程同时更新 x 的元素):

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

在此示例中使用 atomic 指令的优点是它允许并行更新 x 的两个不同元素。 如果改用 critical 指令,则按顺序执行 x 的元素的所有更新(不过不采用任何受保证的顺序)。

atomic 指令仅应用于紧随其后的 C 或 C++ 语句。 因此,此示例中不会以原子方式更新 y 的元素。

A.13 带列表的 flush 指令

以下示例将 flush 指令用于线程对之间特定对象的点对点同步:

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 不带列表的 flush 指令

以下示例(适用于第 2.6.5 节)将受不带列表的 flush 指令影响的共享对象与不受影响的共享对象区分开来:

// 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 使用的线程数

请考虑以下错误的示例(适用于第 3.1.2 节):

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

omp_get_num_threads() 调用会在代码的串行节中返回 1,因此 np 在前面的示例中始终等于 1。 若要确定将为并行区域部署的线程数,调用应处于并行区域内。

以下示例演示如何重写此程序,而不包含对线程数的查询:

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

A.16 锁定

在以下示例中(适用于第 3.2 节),锁定函数的参数应具有类型 omp_lock_t,并且无需刷新它。 锁定函数会导致线程在等待进入第一个 critical 节期间处于空闲状态,但在等待进入第二个节期间执行其他工作。 omp_set_lock 函数会阻止,但 omp_test_lock 函数不会,从而可以执行 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 可嵌套锁定

以下示例(适用于第 3.2 节)演示如何使用可嵌套锁定更新同步到整个结构和其中一个成员。

#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 嵌套的 for 指令

以下 for指令嵌套示例合规,因为内部和外部 for 指令绑定到不同的并行区域:

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

上面示例的以下变体也合规:

#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 显示工作共享指令的不正确嵌套的示例

本节中的示例说明指令嵌套规则。

以下示例不合规,因为内部和外部 for 指令是嵌套的,并绑定到相同的 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);
     }
   }
}

上面示例的以下动态嵌套版本也不合规:

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

以下示例不合规,因为 forsingle 指令是嵌套的,并且它们绑定到相同的并行区域:

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

以下示例不合规,因为 for 内的 barrier 指令可能导致死锁:

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

以下示例不合规,因为 barrier 会导致死锁,原因是一次只有一个线程可以进入 critical 节:

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

以下示例不合规,因为 barrier 会导致死锁,原因是只有一个线程执行 single 节:

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

A.20 绑定 barrier 指令

指令绑定规则要求 barrier 指令绑定到最近的封闭 parallel 指令。 有关指令绑定的详细信息,请参阅第 2.8 节

在以下示例中,从 main 到 sub2 的调用合规,因为 barrier(在 sub3 中)绑定到 sub2 中的并行区域。 从 main 到 sub1 的调用合规,因为 barrier 绑定到子例程 sub2 中的并行区域。 从 main 到 sub3 的调用合规,因为 barrier 不绑定到任何并行区域,会被忽略。 此外,barrier 仅同步封闭并行区域中的线程团队,而不是 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 使用 private 子句设置变量范围

以下示例中 ij 的值在从并行区域退出时未定义:

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

有关 private 子句的详细信息,请参阅第 2.7.2.1 节

A.22 default(none) 子句

以下示例将受 default(none) 子句影响的变量与不受影响的变量区分开来:

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

有关 default 子句的详细信息,请参阅第 2.7.2.5 节

A.23 ordered 指令的示例

可以通过使用 ordered 子句指定的 for 具有许多有序节。 第一个示例不合规,因为 API 指定了以下规则:

“具有 for 构造的循环迭代不能多次执行相同的 ordered 指令,并且不能执行多个 ordered 指令。”(请参阅第 2.6.6 节。)

在此不合规示例中,所有迭代都执行两个有序节:

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

以下合规示例显示具有多个有序节的 for

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

A.24 private 子句的示例

并行区域的 private 子句仅对区域的词法盘区有效,对区域的动态盘区无效。 因此在后面的示例中,在例程 f 的 for 循环中对变量 a 的任何使用都会引用 a 的私有副本,而在例程 g 中使用会引用全局 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 copyprivate 数据特性子句示例

示例 1:copyprivate 子句可用于将单个线程获取的值直接广播到其他线程中私有变量的所有实例。

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

如果从串行区域调用例程 init,则其行为不受指令是否存在的影响。 在一个线程执行了对 get_values 例程的调用后,在所有线程中由 a、b、x 和 y 指定的私有对象都使用读取的值进行定义之前,不会有任何线程离开构造

示例 2:与前面的示例相反,假设读取必须由特定线程执行(例如主线程)。 在这种情况下,copyprivate 子句无法用于直接进行广播,但可用于提供对临时共享对象的访问。

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

示例 3:假设在进入并行区域之前,无法轻松确定区域中所需的锁定对象数。 copyprivate 子句可用于提供对该并行区域中分配的共享锁定对象的访问。

#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 threadprivate 指令

以下示例演示如何使用 threadprivate 指令为每个线程提供单独的计数器。

示例 1

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

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

示例 2

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

A.27 C99 变长数组

以下示例演示如何在 firstprivate 指令中使用 C99 变长数组 (VLA)。

注意

Visual C++ 当前不支持变长数组。

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

A.28 num_threads 子句

以下示例演示 num_threads 子句。 并行区域使用最多 10 个线程来执行。

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

A.29 critical 构造内的工作共享构造

以下示例演示如何在 critical 构造内使用工作共享构造。 此示例合规,因为工作共享构造和 critical 构造未绑定到相同并行区域。

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 重新私有化

以下示例演示变量的重新私有化。 可以在嵌套指令中将私有变量再次标记为 private。 无需在封闭并行区域中共享这些变量。

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

A.31 线程安全的锁定函数

以下 C++ 示例演示如何使用 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 () {}