2. 指令

指令基于 C 和 C++ 标准中定义的 #pragma 指令。 支持 OpenMP C 和 C++ API 的编译器将包含一个命令行选项,用于激活并允许解释所有 OpenMP 编译器指令。

2.1 指令格式

OpenMP 指令语法由附录 C 中的语法正式指定,非正式如下所示:

#pragma omp directive-name  [clause[ [,] clause]...] new-line

每个指令以 #pragma omp 开头,以减少与其他(非 OpenMP 或 OpenMP 的供应商扩展)具有相同名称的 pragma 指令冲突的可能性。 其余指令遵循编译器指令的 C 和 C++ 标准约定。 特别是,可以在 # 前后使用空格,有时必须使用空格来分隔指令中的单词。 在 #pragma omp 之后的预处理令牌受宏替换的约束。

指令区分大小写。 子句出现在指令中的顺序并不重要。 根据每个子句的说明中列出的限制,可以根据需要重复有关指令的子句。 如果子句中出现 variable-list,则它必须仅指定变量。 对于每个指令,只能指定一个 directive-name。 例如,不允许使用以下指令:

/* ERROR - multiple directive names not allowed */
#pragma omp parallel barrier

OpenMP 指令最多适用于一个成功语句,该语句必须是结构化块。

2.2 条件编译

_OPENMP 宏名称由 OpenMP 兼容的实现定义为十进制常量 yyyymm,该常量将是已批准规范的年份和月份。 此宏不得是 #define#undef 预处理指令的主题。

#ifdef _OPENMP
iam = omp_get_thread_num() + index;
#endif

如果供应商定义 OpenMP 扩展,则他们可能会指定其他预定义宏。

2.3 并行构造

以下指令定义并行区域,它是由多个线程并行执行的程序的一个区域。 此指令是启动并行执行的基本构造。

#pragma omp parallel [clause[ [, ]clause] ...] new-line   structured-block

子句为以下其中一项:

  • if( scalar-expression )
  • private( variable-list )
  • firstprivate( variable-list )
  • default(shared | none)
  • shared( variable-list )
  • copyin( variable-list )
  • reduction( operator : variable-list )
  • num_threads( integer-expression )

当线程进入并行构造时,如果下列情况之一为 true,将创建线程团队:

  • 不存在 if 子句。
  • if 表达式的计算结果为非零值。

此线程将成为团队的主线程,线程数为 0,团队中的所有线程(包括主线程)并行执行区域。 如果 if 表达式的值为零,则会序列化区域。

若要确定请求的线程数,请按顺序考虑以下规则。 满足其条件的第一个规则适用:

  1. 如果 num_threads 子句存在,则整数表达式的值是请求的线程数。

  2. 如果已调用 omp_set_num_threads 库函数,则最近执行的调用中的参数值是请求的线程数。

  3. 如果定义了环境变量 OMP_NUM_THREADS,则此环境变量的值是请求的线程数。

  4. 如果未使用上述方法,则请求的线程数是实现定义的。

如果 num_threads 子句存在,那么它将取代 omp_set_num_threads 库函数或 OMP_NUM_THREADS 环境变量请求的线程数,仅适用于它所应用的并行区域。 之后的并行区域不受其影响。

执行并行区域的线程数还取决于是否启用了线程数的动态调整。 如果禁用动态调整,则请求的线程数将执行并行区域。 如果启用动态调整,则请求的线程数是可能执行并行区域的最大线程数。

如果在禁用线程数的动态调整时遇到并行区域,并且为并行区域请求的线程数大于运行时系统可以提供的线程数,则程序的行为是实现定义的。 例如,实现可能会中断程序的执行,也可能序列化并行区域。

omp_set_dynamic 库函数和 OMP_DYNAMIC 环境变量可用于启用或禁用线程数的动态调整。

在任何给定时间实际承载线程的物理处理器数是实现定义的。 创建后,团队中的线程数在该并行区域的持续时间内保持不变。 它可以由用户显式更改,也可以由运行时系统从一个并行区域显式更改到另一个并行区域。

并行区域的动态范围内包含的语句由每个线程执行,每个线程可以执行不同于其他线程的语句路径。 在并行区域的词法范围之外遇到的指令称为孤立指令。

并行区域末尾存在隐式屏障。 只有团队的主线程在并行区域的末尾继续执行。

如果执行并行区域的团队中的线程遇到另一个并行构造,则会创建一个新团队,并成为该新团队的主线程。 嵌套并行区域默认序列化。 因此,默认情况下,嵌套并行区域由一个线程组成的团队执行。 可以使用运行时库函数 omp_set_nested 或环境变量 OMP_NESTED 来更改默认行为。 但是,团队中执行嵌套并行区域的线程数是实现定义的。

parallel 指令的限制如下:

  • 指令上最多显示一个 if 子句。

  • 未指定 if 表达式或 num_threads 表达式中是否会出现任何副作用。

  • 在并行区域中执行的 throw 必须使执行在同一结构化块的动态范围内恢复,并且必须由引发异常的同一线程捕获。

  • 指令上只能显示单个 num_threads 子句。 num_threads 表达式是在并行区域的上下文之外计算的,并且必须计算为正整数值。

  • 未指定 ifnum_threads 子句的计算顺序。

交叉引用

2.4 工作共享构造

工作共享构造将关联语句的执行分配给遇到它的团队成员。 工作共享指令不会启动新线程,并且在进入工作共享构造时没有隐含屏障。

对于团队中的每个线程,遇到的工作共享构造和 barrier 指令序列必须相同。

OpenMP 定义了以下工作共享构造,以下各节介绍了这些构造:

2.4.1 for 构造

for 指令标识迭代工作共享构造,该构造指定将并行执行的关联循环的迭代。 for 循环的迭代在团队中已经存在的线程之间分布,该团队执行其绑定到的并行构造。 for 构造的语法如下所示:

#pragma omp for [clause[[,] clause] ... ] new-line for-loop

子句为以下其中一项:

  • private( variable-list )
  • firstprivate( variable-list )
  • lastprivate( variable-list )
  • reduction( operator : variable-list )
  • ordered
  • schedule( kind [, chunk_size] )
  • nowait

for 指令对相应 for 循环的结构进行了限制。 具体而言,相应的 for 循环必须具有规范形状:

for ( init-expr ; var logical-op b ; incr-expr )

init-expr
下列类型作之一:

  • var = lb
  • integer-type var = lb

incr-expr
下列类型作之一:

  • ++ var
  • var ++
  • -- var
  • --var
  • var += incr
  • var -= incr
  • var = var + incr
  • var = incr + var
  • var = var - incr

var
一个带符号整数变量。 否则,如果共享此变量,那么在 for 期间它将隐式地设置为私有。 请勿在 for 语句正文中修改此变量。 除非指定 lastprivate 变量,否则循环后的值不确定。

logical-op
下列类型作之一:

  • <
  • <=
  • >
  • >=

lb、b 和 incr
循环固定整数表达式。 这些表达式的计算过程中没有同步,因此任何计算的副作用都会产生不确定的结果。

规范形式允许在循环的条目上计算循环迭代数。 此计算是在整型提升后使用 var 类型的值进行的。 具体而言,如果 b-lb+incr 的值不能在该类型中表示,则结果不确定。 此外,如果 logical-op<<=,则 incr-expr 必须在循环的每个迭代上增加 var。 如果 logical-op>>=,则 incr-expr 必须在循环的每个迭代上减小 var

schedule 子句指定在团队线程之间如何划分 for 循环迭代。 程序的正确性不得取决于哪个线程执行特定迭代。 如果指定,则 chunk_size 的值必须是具有正值的循环固定整数表达式。 此表达式的计算过程中没有同步,因此任何计算的副作用都会产生不确定的结果。 schedule kind 可以是下列值之一:

表 2-1:schedule 子句 kind

说明
static 指定 schedule(static,chunk_size) 时,迭代被划分为由 chunk_size 指定的大小的区块。 区块按照线程编号的顺序以轮循机制方式静态分配给团队中的线程。 如果未指定 chunk_size,则迭代空间将划分为大小大致相等的区块,每个线程分配一个区块。
dynamic 指定 schedule(dynamic,chunk_size) 时,迭代被划分为一系列区块,每个区块包含 chunk_size 迭代。 每个区块都分配给正在等待分配的线程。 该线程执行迭代区块,然后等待下一个分配,直到没有要分配的区块为止。 要分配的最后一个区块可能具有较少的迭代数。 如果未指定 chunk_size,则默认为 1。
引导式 指定 schedule(guided,chunk_size) 后,迭代将分配给区块中的线程(大小逐渐减小)。 当线程完成其分配的迭代区块时,它会动态分配另一个区块,直到没有可分配的区块。 对于 chunk_size 1,每个区块的大小大约是未分配迭代数除以线程数。 这些大小几乎呈指数级递减到 1。 对于值 k 大于 1 的 chunk_size,大小几乎呈指数递减到 k,但最后一个区块的迭代数可能小于 k。 如果未指定 chunk_size,则默认为 1。
运行时 指定 schedule(runtime) 后,有关计划的决定将推迟到运行时。 可以通过设置环境变量 OMP_SCHEDULE 在运行时选择 schedule kind 和区块大小。 如果未设置此环境变量,则生成的计划是实现定义的。 指定 schedule(runtime) 时,不得指定 chunk_size

如果没有显式定义的 schedule 子句,则默认 schedule 为实现定义。

符合 OpenMP 的程序不应依赖于特定计划来正确执行。 程序不应依赖于完全符合上述说明的 schedule kind,因为同样的 schedule kind 在不同编译器中可能有不同的实现。 这些说明可用于选择适合特定情况的计划。

ordered 指令绑定到 for 构造时,必须存在 ordered 子句。

for 构造末尾存在隐式屏障,除非指定 nowait 子句。

for 指令的限制如下:

  • for 循环必须是结构化块,此外,其执行不得由 break 语句终止。

  • for 指令关联的 for 循环的循环控件表达式的值对于团队中的所有线程必须相同。

  • for 循环迭代变量必须具有带符号整数类型。

  • for 指令上只能显示单个 schedule 子句。

  • for 指令上只能显示单个 ordered 子句。

  • for 指令上只能显示单个 nowait 子句。

  • 对于 chunk_size、lb、b 或 incr 表达式中是否发生任何副作用或其发生频率未明确说明。

  • 对于团队中的所有线程,chunk_size 表达式的值必须相同。

交叉引用

2.4.2 sections 构造

sections 指令标识非迭代工作共享构造,该构造指定要在团队线程中划分的一组构造。 每个 section 由团队线程执行一次。 sections 指令的语法如下所示:

#pragma omp sections [clause[[,] clause] ...] new-line
   {
   [#pragma omp section new-line]
      structured-block
   [#pragma omp section new-linestructured-block ]
...
}

子句为以下其中一项:

  • private( variable-list )
  • firstprivate( variable-list )
  • lastprivate( variable-list )
  • reduction( operator : variable-list )
  • nowait

每个 section 前面都有一个 section 指令,尽管 section 指令对于第一个 section 是可选的。 section 指令必须出现在 sections 指令的词法范围内。 sections 构造末尾存在隐式屏障,除非指定 nowait

sections 指令的限制如下:

  • section 指令不得出现在 sections 指令的词法范围之外。

  • sections 指令上只能显示单个 nowait 子句。

交叉引用

  • privatefirstprivatelastprivatereduction 子句(第 2.7.2 节

2.4.3 single 构造

single 指令标识一个构造,该构造指定团队中只有一个线程执行关联的结构化块(不一定是主线程)。 single 指令的语法如下所示:

#pragma omp single [clause[[,] clause] ...] new-linestructured-block

子句为以下其中一项:

  • private( variable-list )
  • firstprivate( variable-list )
  • copyprivate( variable-list )
  • nowait

single 构造后面存在隐式屏障,除非指定 nowait 子句。

single 指令的限制如下:

  • single 指令上只能显示单个 nowait 子句。
  • copyprivate 子句不得与 nowait 子句一起使用。

交叉引用

  • privatefirstprivatecopyprivate 子句(第 2.7.2 节

2.5 组合的并行工作共享构造

组合的并行工作共享构造是指定只有一个工作共享构造的并行区域的快捷方式。 这些指令的语义与显式指定 parallel 指令后跟单个工作共享构造相同。

以下部分介绍了组合的并行工作共享构造:

2.5.1 parallel for 构造

parallel for 指令是仅包含单个 for 指令的 parallel 区域的快捷方式。 parallel for 指令的语法如下所示:

#pragma omp parallel for [clause[[,] clause] ...] new-linefor-loop

此指令允许 parallel 指令和 for 指令的所有子句(但 nowait 子句除外),它们具有相同的含义和限制。 语义与显式指定 parallel 指令后跟指令 for 相同。

交叉引用

2.5.2 parallel sections 构造

parallel sections 指令提供一个快捷方式窗体,用于指定只有单个 sections 指令的 parallel 区域。 语义与显式指定 parallel 指令后跟指令 sections 相同。 parallel sections 指令的语法如下所示:

#pragma omp parallel sections  [clause[[,] clause] ...] new-line
   {
   [#pragma omp section new-line]
      structured-block
   [#pragma omp section new-linestructured-block  ]
   ...
}

子句可以是 parallelsections 指令接受的子句之一,但 nowait 子句除外。

交叉引用

2.6 Master 和同步指令

以下几节将介绍:

2.6.1 master 构造

master 指令标识一个构造,该构造指定由团队的主线程执行的结构化块。 master 指令的语法如下所示:

#pragma omp master new-linestructured-block

团队中的其他线程不会执行关联的结构化块。 进入或退出主构造时没有隐式屏障。

2.6.2 critical 构造

critical 指令标识一个构造,该构造一次将关联的结构化块的执行限制为单个线程。 critical 指令的语法如下所示:

#pragma omp critical [(name)]  new-linestructured-block

可选名称可用于标识关键区域。 用于标识关键区域的标识符具有外部链接,并且位于与标签、标记、成员和普通标识符所使用的命名空间分开的命名空间中。

线程在关键区域的开头等待,直到没有其他线程在执行同名关键区域(程序中的任何位置)。 所有未命名 critical 指令都映射到相同的未指定名称。

2.6.3 barrier 指令

barrier 指令同步团队中的所有线程。 遇到该指令时,团队中的每个线程都将等待,直到其他所有线程都达到此点。 barrier 指令的语法如下所示:

#pragma omp barrier new-line

在团队中的所有线程都遇到 barrier 后,团队中的每个线程将开始行执行 barrier 指令之后的语句。 由于 barrier 指令没有 C 语言语句作为其语法的一部分,因此对其在程序中的位置有一些限制。 有关正式语法的详细信息,请参阅附录 C。下面的示例说明了这些限制。

/* ERROR - The barrier directive cannot be the immediate
*          substatement of an if statement
*/
if (x!=0)
   #pragma omp barrier
...

/* OK - The barrier directive is enclosed in a
*      compound statement.
*/
if (x!=0) {
   #pragma omp barrier
}

2.6.4 atomic 构造

atomic 指令可确保特定内存位置以原子方式更新,而不是将其公开给可能多个同时写入的线程。 atomic 指令的语法如下所示:

#pragma omp atomic new-lineexpression-stmt

表达式语句必须具有以下形式之一:

  • x binop = expr
  • x ++
  • ++ x
  • x --
  • -- x

在前面的表达式中:

  • x 是标量类型的 lvalue 表达式。

  • expr 是标量类型的表达式,它不引用 x 指定的对象。

  • binop 不是重载运算符,并且是 +*-/&^|<<>> 中的一个。

虽然实现是否将所有 atomic 指令替换为具有相同唯一名称critical 指令是实现定义的,但 atomic 指令允许更好的优化。 通常可以使用硬件指令,以最少的开销执行原子更新。

只有 x 指定的对象的加载和存储是原子式;expr 的计算不是原子式。 为了避免争用条件,应使用 atomic 指令保护并行位置的所有更新,但已知没有争用条件的更新除外。

atomic 指令的限制如下:

  • 在整个程序中,对存储位置 x 的所有原子引用都需要有兼容类型。

示例

extern float a[], *p = a, b;
/* Protect against races among multiple updates. */
#pragma omp atomic
a[index[i]] += b;
/* Protect against races with updates through a. */
#pragma omp atomic
p[i] -= 1.0f;

extern union {int n; float x;} u;
/* ERROR - References through incompatible types. */
#pragma omp atomic
u.n++;
#pragma omp atomic
u.x -= 1.0f;

2.6.5 flush 指令

flush 指令(无论是显式还是隐式)都指定了一个“跨线程”序列点,该序列点需要此实现,以确保团队中的所有线程对于内存中的某些对象(下面指定)具有一致的视图。 这意味着引用这些对象的表达式的先前计算已完成,而后续计算尚未开始。 例如,编译器必须将对象的值从寄存器还原到内存,硬件可能需要将写入缓冲区刷新到内存,并从内存中重新加载对象的值。

flush 指令的语法如下所示:

#pragma omp flush [(variable-list)]  new-line

如果需要同步的对象都可以由变量指定,则可以在可选 variable-list 中指定这些变量。 如果 variable-list 存在指针,则刷新指针本身,而不是指针引用的对象。

没有 variable-listflush 指令会将除不可访问的对象之外的所有共享对象与自动存储持续时间同步。 (这可能比带 variable-listflush 开销更大。)下面的指令隐含了一个没有 variable-listflush 指令:

  • barrier
  • 进入和退出 critical
  • 进入和退出 ordered
  • 进入和退出 parallel
  • 退出 for
  • 退出 sections
  • 退出 single
  • 进入和退出 parallel for
  • 进入和退出 parallel sections

如果存在 nowait 子句,则不隐含该指令。 需要注意的是,以下任何情况不隐含 flush 指令:

  • 进入 for
  • 进入或退出 master
  • 进入 sections
  • 进入 single

对于使用类型为易失性限定的对象值的引用,它的行为就像在上一个序列点有一个指定该对象的 flush 指令一样。 对于修改类型为易失性限定的对象值的引用,它的行为就像在下一个序列点有一个指定该对象的 flush 指令一样。

由于 flush 指令没有 C 语言语句作为其语法的一部分,因此对其在程序中的位置有一些限制。 有关正式语法的详细信息,请参阅附录 C。下面的示例说明了这些限制。

/* ERROR - The flush directive cannot be the immediate
*          substatement of an if statement.
*/
if (x!=0)
   #pragma omp flush (x)
...

/* OK - The flush directive is enclosed in a
*      compound statement
*/
if (x!=0) {
   #pragma omp flush (x)
}

flush 指令的限制如下:

  • flush 指令中指定的变量不得有引用类型。

2.6.6 ordered 构造

按照顺序循环中执行迭代的顺序执行 ordered 指令后的结构化块。 ordered 指令的语法如下所示:

#pragma omp ordered new-linestructured-block

指令 ordered 必须在 forparallel for 构造的动态范围内。 ordered构造绑定的 forparallel for 指令必须指定 ordered 子句,如第 2.4.1 节中所述。 在使用 for 子句执行 parallel forordered 构造时,ordered 构造将严格按照循环顺序执行的顺序执行。

ordered 指令的限制如下:

  • 具有 for 构造的循环迭代不能多次执行相同的有序指令,并且不能执行多个 ordered 指令。

2.7 数据环境

本部分提供一个指令和多个子句,用于在并行区域执行期间控制数据环境,如下所示:

  • 提供了 threadprivate 指令,使文件范围、命名空间范围或静态块范围变量成为线程的局部变量。

  • 第 2.7.2 节介绍了在并行构造或工作共享构造期间,可以在指令上指定用于控制变量的共享属性的子句。

2.7.1 threadprivate 指令

threadprivate 指令将 variable-list 中指定的命名文件范围、命名空间范围或静态块范围变量设为线程的私有变量。 variable-list 是没有不完整类型的变量的逗号分隔列表。 threadprivate 指令的语法如下所示:

#pragma omp threadprivate(variable-list) new-line

threadprivate 变量的每个副本在对副本的第一次引用之前在程序中的未指定点初始化一次,并按照通常的方式进行(即主控副本将在程序的串行执行中初始化)。 请注意,如果在 threadprivate 变量的显式初始化表达式中引用对象,并且在对变量副本的第一次引用之前修改变量值,则行为未指定。

与任何私有变量一样,线程不得引用另一个线程的 threadprivate 对象副本。 在程序的串行区域和主区域期间,引用将指向主线程的对象副本。

执行第一个并行区域后,仅当已禁用动态线程机制并且所有并行区域的线程数保持不变时,才能保证 threadprivate 对象中的数据能够持久保存。

threadprivate 指令的限制如下:

  • 文件范围或命名空间范围变量的 threadprivate 指令必须出现在任何定义或声明之外,并且必须在对其列表中任何变量的所有引用之前按词法表示。

  • 文件或命名空间范围内 threadprivate 指令 variable-list 中的每个变量都必须引用在词法上位于该指令前面的文件或命名空间范围内的变量声明。

  • 静态块范围变量的 threadprivate 指令必须出现在变量范围内,而不是嵌套范围内。 指令必须按词法位于列表中对任何变量的所有引用之前。

  • 块范围中 threadprivate 指令的 variable-list 中的每个变量都必须引用相同范围内在词法上位于该指令前的变量声明。 变量声明必须使用静态存储类说明符。

  • 如果在一个转换单元的 threadprivate 指令中指定变量,则必须在声明该变量的每个转换单元的 threadprivate 指令中指定它。

  • threadprivate 变量不得出现在除 copyincopyprivateschedulenum_threadsif 子句以外的任何子句中。

  • threadprivate 变量的地址不是地址常量。

  • threadprivate 变量不得有不完整的类型或引用类型。

  • 如果具有非 POD 类类型的 threadprivate 变量使用显式初始化表达式声明,则该变量必须具有可访问且明确的复制构造函数。

以下示例演示修改初始化表达式中显示的变量会如何导致未指定的行为,以及如何使用辅助对象和复制构造函数避免此问题。

int x = 1;
T a(x);
const T b_aux(x); /* Capture value of x = 1 */
T b(b_aux);
#pragma omp threadprivate(a, b)

void f(int n) {
   x++;
   #pragma omp parallel for
   /* In each thread:
   * Object a is constructed from x (with value 1 or 2?)
   * Object b is copy-constructed from b_aux
   */
   for (int i=0; i<n; i++) {
      g(a, b); /* Value of a is unspecified. */
   }
}

交叉引用

2.7.2 数据共享特性子句

多个指令接受子句,允许用户在区域持续时间内控制变量的共享属性。 共享属性子句仅适用于子句出现的指令的词法范围内的变量。 并非所有以下子句都允许在所有指令上使用。 对特定指令有效的子句列表在指令中进行了说明。

如果遇到并行或工作共享构造时显示变量,并且该变量未在共享属性子句或 threadprivate 指令中指定,则共享该变量。 共享在并行区域的动态范围内声明的静态变量。 共享堆分配的内存(例如,在 C 或 C++ 中使用 malloc() 或在 C++ 中使用 new 操作符)。 (但是,指向此内存的指针可以是私有,也可以共享。)在并行区域的动态范围内声明的具有自动存储持续时间的变量是私有的。

大多数子句都接受 variable-list 参数,该参数是逗号分隔的可见变量列表。 如果在数据共享属性子句中引用的变量具有派生自模板的类型,并且程序中没有对该变量的其他引用,则行为是未定义的。

指令子句中显示的所有变量都必须可见。 子句可以根据需要重复,但不能在多个子句中指定变量,除非变量可以同时在 firstprivatelastprivate 子句中指定。

以下部分介绍数据共享属性子句:

2.7.2.1 private

private 子句将 variable-list 中的变量声明为团队中每个线程的私有变量。 private 子句的语法如下:

private(variable-list)

private 子句中指定的变量的行为如下所示。 为构造分配具有自动存储持续时间的新对象。 新对象的大小和对齐方式由变量类型决定。 此分配为团队中的每个线程进行一次,必要时为类对象调用默认构造函数;否则初始值不确定。 变量引用的原始对象在进入构造时具有不确定值,不能在构造的动态范围内修改,并且在退出构造时具有不确定值。

在指令构造的词法范围内,变量引用线程分配的新私有对象。

private 子句的限制如下:

  • 具有 private 子句中指定的类类型的变量必须具有可访问且明确的默认构造函数。

  • private 子句中指定的变量不得有 const 限定类型,除非它具有包含 mutable 成员的类类型。

  • private 子句中指定的变量不得有不完整的类型或引用类型。

  • parallel 指令的 reduction 子句中出现的变量不能在 private 子句中指定,后者位于绑定到并行构造的工作共享指令上。

2.7.2.2 firstprivate

firstprivate 子句提供 private 子句提供的功能超集。 firstprivate 子句的语法如下:

firstprivate(variable-list)

variable-list 中指定的变量具有 private 子句语义,如第 2.7.2.1 节中所述。 初始化或构造就像在线程执行构造之前每个线程完成一次一样。 对于并行构造上的 firstprivate 子句,新私有对象的初始值是紧邻遇到它的线程并行构造之前存在的原始对象的值。 对于工作共享构造上的 firstprivate 子句,执行工作共享构造的每个线程的新私有对象初始值是同一线程遇到工作共享构造之前存在的原始对象的值。 此外,对于 C++ 对象,每个线程的新私有对象都是从原始对象构造的。

firstprivate 子句的限制如下:

  • firstprivate 子句中指定的变量不得有不完整的类型或引用类型。

  • 具有指定为 firstprivate 的类类型的变量必须具有可访问且明确的复制构造函数。

  • 在并行区域内为私有的变量或 parallel 指令的 reduction 子句中出现的变量不能在 firstprivate 子句中指定,后者位于绑定到并行构造的工作共享指令上。

2.7.2.3 lastprivate

lastprivate 子句提供 private 子句提供的功能超集。 lastprivate 子句的语法如下:

lastprivate(variable-list)

variable-list 中指定的变量具有 private 子句语义。 当 lastprivate 子句出现在标识工作共享构造的指令上时,将关联循环的按顺序排列的上一次迭代或词法上最后一节指令中每个 lastprivate 变量的值分配给变量的原始对象。 未按 forparallel for 的上一次迭代或词法上 sectionsparallel sections 的最后一节分配值的变量在构造后具有不确定的值。 未分配的子对象在构造后也会得到不确定的值。

lastprivate 子句的限制如下:

  • private 的所有限制适用。

  • 具有指定为 lastprivate 的类类型的变量必须具有可访问且明确的复制赋值运算符。

  • 在并行区域内为私有的变量或 parallel 指令的 reduction 子句中出现的变量不能在 lastprivate 子句中指定,后者位于绑定到并行构造的工作共享指令上。

2.7.2.4 shared

此子句在团队的所有线程中共享出现在 variable-list 中的变量。 团队中的所有线程都访问 shared 变量的同一存储区域。

shared 子句的语法如下:

shared(variable-list)

2.7.2.5 default

default 子句允许用户影响变量的数据共享属性。 default 子句的语法如下:

default(shared | none)

指定 default(shared) 等效于显式列出 shared 子句中每个当前可见的变量,除非它是 threadprivateconst 限定。 如果缺少显式 default 子句,则默认行为与指定 default(shared) 相同。

指定 default(none) 要求对并行构造的词法范围内变量的每次引用,必须至少满足以下条件之一:

  • 该变量在包含引用的构造的数据共享属性子句中显式列出。

  • 变量在并行构造中声明。

  • 变量为 threadprivate

  • 该变量具有 const 限定类型。

  • 该变量是一个 for 循环的循环控件变量,该循环紧跟在 forparallel for 指令之后,变量引用显示在循环中。

在封闭指令的 firstprivatelastprivatereduction 子句上指定变量会导致在封闭上下文中隐式引用该变量。 此类隐式引用也受上面列出的要求的约束。

parallel 指令上只能指定一个 default 子句。

变量的默认数据共享属性可以通过使用 privatefirstprivatelastprivatereductionshared 子句来重写,如下所示:

#pragma  omp  parallel  for  default(shared)  firstprivate(i)\
   private(x)  private(r)  lastprivate(i)

2.7.2.6 reduction

此子句使用运算符 op 对 variable-list 中显示的标量变量执行缩减。 reduction 子句的语法如下:

reduction( op : variable-list )

通常为具有以下形式之一的语句指定缩减:

  • x = x op expr
  • x binop = expr
  • x=expropx(减法除外)
  • x ++
  • ++ x
  • x --
  • -- x

其中:

x
列表中指定的缩减变量之一。

variable-list
逗号分隔的标量缩减变量列表。

expr
具有不引用 x 的标量类型的表达式。

op
不是重载运算符,而是 +*-&^|&&|| 中的一个。

binop
不是重载运算符,而是 +*-&^| 中的一个。

下面是 reduction 子句的示例:

#pragma omp parallel for reduction(+: a, y) reduction(||: am)
for (i=0; i<n; i++) {
   a += b[i];
   y = sum(y, c[i]);
   am = am || b[i] == c[i];
}

如示例中所示,运算符可能在函数调用中隐藏。 用户应注意子句 reduction 子句中指定的运算符与缩减操作匹配。

虽然 || 运算符的右操作数在此示例中没有副作用,但它们是允许的,应谨慎使用。 在这种情况下,在顺序执行循环时保证不会发生的副作用可能会在并行执行时发生。 出现这种差异是因为迭代的执行顺序不确定。

该运算符用于确定编译器用于缩减的任何私有变量的初始值,并确定终止运算符。 显式指定运算符将允许缩减语句在构造的词法范围之外。 可以在指令上指定任意数量的 reduction 子句,但该指令的变量最多可以出现在一个 reduction 子句中。

创建 variable-list 中每个变量的私有副本,每个线程一个,就像使用了 private 子句一样。 根据运算符初始化私有副本(请参阅下表)。

在指定 reduction 子句的区域末尾,将更新原始对象,以反映使用指定运算符将原始值与每个私有副本的最终值相结合的结果。 缩减运算符都是关联的(减法除外),编译器可以自由重新关联最终值的计算。 (将添加减法的部分结果以构成最终值。)

当第一个线程到达包含子句时,原始对象的值将变得不确定,并且一直保持到缩减计算完成。 通常,计算将在构造结束时完成;但是,如果 reduction 子句用于应用 nowait 的构造,则原始对象的值将保持不确定状态,直到执行屏障同步以确保所有线程均已完成 reduction 子句。

下表列出了有效运算符及其规范初始化值。 实际初始化值将与缩减变量的数据类型一致。

操作员 初始化
+ 0
* 1
- 0
& ~0
| 0
^ 0
&& 1
|| 0

reduction 子句的限制如下:

  • reduction 子句中变量的类型必须对缩减运算符有效,但不允许使用指针类型和引用类型。

  • reduction 子句中指定的变量不得为 const 限定。

  • 在并行区域内为私有的变量或 parallel 指令的 reduction 子句中出现的变量不能在 reduction 子句中指定,后者位于绑定到并行构造的工作共享指令上。

    #pragma omp parallel private(y)
    { /* ERROR - private variable y cannot be specified
                  in a reduction clause */
        #pragma omp for reduction(+: y)
        for (i=0; i<n; i++)
           y += b[i];
    }
    
    /* ERROR - variable x cannot be specified in both
                a shared and a reduction clause */
    #pragma omp parallel for shared(x) reduction(+: x)
    

2.7.2.7 copyin

copyin 子句提供一种机制,用于为执行并行区域的团队中每个线程的 threadprivate 变量分配相同值。 对于 copyin 子句中指定的每个变量,团队主线程中变量的值将被复制到并行区域开头的线程私有副本,就像通过赋值那样。 copyin 子句的语法如下:

copyin(
variable-list
)

copyin 子句的限制如下:

  • copyin 子句中指定的变量必须具有可访问且明确的复制赋值运算符。

  • copyin 子句中指定的变量必须是 threadprivate 变量。

2.7.2.8 copyprivate

copyprivate 子句提供一种机制,使用私有变量将值从团队的一个成员广播到其他成员。 在难以提供这样的共享变量时(例如,在每个级别需要不同变量的递归中),这是对值使用共享变量的替代方法。 copyprivate 子句只能出现在 single 指令上。

copyprivate 子句的语法如下:

copyprivate(
variable-list
)

copyprivate 子句对其 variable-list 中变量的影响发生在与 single 构造关联的结构化块执行之后,以及在构造结束时团队中的任何线程离开屏障之前。 然后,在团队的所有其他线程中,对于 variable-list 中的每个变量,该变量将使用执行该构造的结构化块的线程中相应变量的值来定义(就像通过赋值一样)。

copyprivate 子句的限制如下:

  • copyprivate 子句中指定的变量不得出现在同一 single 指令的 privatefirstprivate 子句中。

  • 如果在并行区域的动态范围内遇到具有 copyprivate 子句的 single 指令,则 copyprivate 子句中指定的所有变量都必须在封闭上下文中私有。

  • copyprivate 子句中指定的变量必须具有可访问且明确的复制赋值运算符。

2.8 指令绑定

指令的动态绑定必须遵循以下规则:

  • forsectionssinglemasterbarrier 指令绑定到动态封闭的 parallel(如果存在),而不考虑该指令中可能存在的任何 if 子句的值。 如果当前未执行并行区域,则指令由仅由主线程组成的团队执行。

  • ordered 指令绑定到动态封闭 for

  • atomic 指令对所有线程中的 atomic 指令强制实施独占访问,而不仅仅是当前团队。

  • critical 指令对所有线程中的 critical 指令强制实施独占访问,而不仅仅是当前团队。

  • 指令永远不能绑定到最近的动态封闭的 parallel 之外的任何指令。

2.9 指令嵌套

指令的动态嵌套必须遵循以下规则:

  • 在另一个 parallel 指令中的 parallel 指令会在逻辑上建立一个新团队,该团队只由当前线程组成,除非启用嵌套并行。

  • 绑定到同一个 parallelforsectionssingle 不允许相互嵌套。

  • 具有相同名称的 critical 指令不允许相互嵌套。 请注意,此限制不足以阻止死锁。

  • 如果 forsectionssingle 指令绑定到与 criticalorderedmaster 区域相同的 parallel,那么这些指令不允许在这些区域的动态范围内。

  • 如果 barrier 指令绑定到与 fororderedsectionssinglemastercritical 区域相同的 parallel,那么这些指令不允许在这些区域的动态范围内。

  • 如果 master 指令绑定到与工作共享指令相同的 parallel,那么 master 指令不允许在 forsectionssingle 区域的动态范围内。

  • 如果 ordered 指令绑定到与 critical 区域相同的 parallel,那么这些指令不允许在这些区域的动态范围内。

  • 在并行区域中动态执行时允许的任何指令也允许在并行区域外执行。 在用户指定的并行区域外动态执行时,指令由仅由主线程组成的团队执行。