Oracle® Developer Studio 12.5:OpenMP API 用户指南

退出打印视图

更新时间: 2016 年 7 月
 
 

4.7 OpenMP 编程注意事项

任务处理功能使 OpenMP 程序的复杂性有所增加。本节讨论一些要考虑的与任务相关的编程问题。

4.7.1 Threadprivate 和线程特定的信息

当线程遇到任务调度点时,实现可能会暂停当前任务并安排线程处理另一个任务。该行为意味着任务中的 threadprivate 变量或其他线程特定的信息(如线程数)在任务调度点前后可能会有所不同。

如果暂停的任务为绑定 (tied) 任务,则恢复执行该任务的线程与暂停该任务的线程将是同一线程。因此,恢复该任务后,线程数将保持相同。但是,threadprivate 变量的值可能会更改,原因是可能会安排线程处理另一个任务,这样会在恢复暂停的任务之前修改 threadprivate 变量。

如果暂停的任务为非绑定 (untied) 任务,则恢复执行该任务的线程可能与暂停该任务的线程不同。因此,线程数和 threadprivate 变量的值在任务调度点之前和之后都可能不同。

4.7.2 OpenMP 锁

从 OpenMP 3.0 开始,锁为任务所有,而不再由线程所有。某个任务获取锁之后,该任务就会拥有该锁,同一任务必须在任务完成前释放锁。但是,critical 构造仍然保留了基于线程的互斥机制。

由于锁为任务所有,因此使用锁时应格外小心。下例符合 OpenMP 2.5 规范,因为在并行区域中释放锁 lck 的线程与在该程序顺序部分中获取锁的线程相同。并行区域的主线程与初始线程相同。但是,该示例不符合最近的规范,因为释放锁 lck 的任务区域不是获取锁的任务区域。

示例 9  OpenMP 3.0 之前的锁应用
#include <stdlib.h>
#include <stdio.h>
#include <omp.h>

int main()
{
  int x;
  omp_lock_t lck;

  omp_init_lock (&lck);
  omp_set_lock (&lck);
  x = 0;

  #pragma omp parallel shared (x)
  {
    #pragma omp master
    {
      x = x + 1;
      omp_unset_lock (&lck);
    }
  }
  omp_destroy_lock (&lck);
}

4.7.3 对堆栈数据的引用

任务可以引用任务构造所在例程(主机例程)的堆栈中的数据。由于任务的执行可能会延迟到下一个隐式或显式屏障,所以任务有可能在主机例程的堆栈已经弹出,堆栈数据被覆盖(从而销毁任务引用的堆栈数据)之后执行。

确保插入所需的同步,以便当任务引用变量时,这些变量仍在堆栈中,如本节中的两个示例所示。

示例 10中,在 task 构造中将 i 指定为 shared,任务会访问在 work() 例程的堆栈中分配的 i 的副本。

任务的执行可能会延迟,使得任务将在 work() 例程已返回后,在 main() 中的并行区域末尾的隐式屏障处执行。那时,当任务引用 i 时,它会访问当时碰巧在堆栈中不确定的某个值。

为了获得正确的结果,请确保 work() 不在任务完成前返回。通过将 taskwait 指令插在 task 构造之后可以实现此目标,如示例 11中所示。或者,可以在 task 构造中将 i 指定为 firstprivate 而不是 shared

示例 10  堆栈数据:引用不正确
#include <stdio.h>
#include <omp.h>

void work()
 {
   int i;

   i = 10;
   #pragma omp task shared(i)
   {
     #pragma omp critical
     printf("In Task, i = %d\n",i);
   }
 }

int main(int argc, char** argv)
 {
    omp_set_num_threads(8);
    omp_set_dynamic(0);

    #pragma omp parallel 
    {
      work();
    }
 }
示例 11  堆栈数据:引用正确
#include <stdio.h>
#include <omp.h>

void work()
 {
   int i;

   i = 10;
   #pragma omp task shared(i)
   {
     #pragma omp critical
     printf("In Task, i = %d\n",i);
   }

   /* Use TASKWAIT for synchronization. */
   #pragma omp taskwait
 }

int main(int argc, char** argv)
 {
    omp_set_num_threads(8);
    omp_set_dynamic(0);

    #pragma omp parallel 
    {
      work();
    }
 }

在以下示例中,task 构造中的 j 引用 sections 构造中的 j。因此,任务会访问 sections 构造中 jfirstprivate 副本,该副本在 Oracle Developer Studio 中是 sections 构造的概要例程的堆栈中的局部变量。

任务的执行可能会延迟,使得任务将在 sections 构造的概要例程退出后,在 sections 区域末尾的隐式屏障处执行。因此当任务引用 j 时,会访问堆栈中的某个不确定的值。

为了得到正确的结果,请确保任务在 sections 区域达到其隐式屏障前执行,这可以通过在 task 构造之后插入 taskwait 指令来实现,如示例 13 中所示。或者,可以在 task 构造中将 j 指定为 firstprivate 而不是 shared

示例 12  段数据:引用不正确
#include <stdio.h>
#include <omp.h>

int main(int argc, char** argv)
 {
    omp_set_num_threads(2);
    omp_set_dynamic(0);
    int j=100;

    #pragma omp parallel shared(j)
    {
       #pragma omp sections firstprivate(j)
       {
          #pragma omp section
          {
             #pragma omp task shared(j)
             {
               #pragma omp critical
               printf("In Task, j = %d\n",j);
             }
          }
       } /* Implicit barrier for sections */
    } /* Implicit barrier for parallel */

    printf("After parallel, j = %d\n",j);
 }
示例 13  段数据:引用正确
#include <stdio.h>
#include <omp.h>

int main(int argc, char** argv)
 {
    omp_set_num_threads(2);
    omp_set_dynamic(0);
    int j=100;

    #pragma omp parallel shared(j)
    {
       #pragma omp sections firstprivate(j)
       {
          #pragma omp section
          {
             #pragma omp task shared(j)
             {
               #pragma omp critical
               printf("In Task, j = %d\n",j);
             }

             /* Use TASKWAIT for synchronization. */
             #pragma omp taskwait
          }
       } /* Implicit barrier for sections */
    }/* Implicit barrier for parallel */

    printf("After parallel, j = %d\n",j);
 }