3. 运行时库函数
本部分介绍 OpenMP C 和 C++ 运行时库函数。 标头 <omp.h> 声明两种类型:多个可用于控制和查询并行执行环境的函数,以及可用于同步访问数据的锁函数。
类型 omp_lock_t
是一种对象类型,可以表示锁可用或线程拥有锁。 这些锁称为简单锁。
类型 omp_nest_lock_t
是一种对象类型,既可以表示锁可用,也可以表示拥有锁的线程的标识和嵌套计数(如下所述)。 这些锁称为可嵌套锁。
库函数是具有“C”链接的外部函数。
本章中的说明分为以下主题:
3.1 执行环境函数
本部分中描述的函数会影响和监视线程、处理器和并行环境:
- omp_set_num_threads
- omp_get_num_threads
- omp_get_max_threads
- omp_get_thread_num
- omp_get_num_procs
- omp_in_parallel
- omp_set_dynamic
- omp_get_dynamic
- omp_set_nested
- omp_get_nested
3.1.1 omp_set_num_threads 函数
omp_set_num_threads
函数设置用于未指定 num_threads
子句的后续并行区域的默认线程数。 格式如下所示:
#include <omp.h>
void omp_set_num_threads(int num_threads);
参数 num_threads 的值必须是正整数。 其效果取决于是否启用了线程数的动态调整。 有关 omp_set_num_threads
函数和线程动态调整之间的交互的完整规则集,请参阅第 2.3 部分。
当从 omp_in_parallel
函数返回零的程序部分调用时,此函数具有上述效果。 如果从 omp_in_parallel
函数返回非零值的程序部分调用,则此函数的行为未定义。
此调用优先于 OMP_NUM_THREADS
环境变量。 可以通过调用 omp_set_num_threads
或设置 OMP_NUM_THREADS
环境变量来建立线程数的默认值,而该默认值可以通过在单个 parallel
指令上指定 num_threads
子句进行显式重写。
有关详细信息,请参阅 omp_set_dynamic。
交叉引用
- omp_set_dynamic 函数
- omp_get_dynamic 函数
- OMP_NUM_THREADS 环境变量
- num_threads 子句
3.1.2 omp_get_num_threads 函数
omp_get_num_threads
函数返回团队中当前的线程数,该团队正在执行从中调用函数的并行区域。 格式如下所示:
#include <omp.h>
int omp_get_num_threads(void);
num_threads
子句、omp_set_num_threads
函数和 OMP_NUM_THREADS
环境变量控制团队中的线程数。
如果用户未显式设置线程数,则默认值由实现定义。 此函数绑定到最近的封闭 parallel
指令。 如果从程序的串行部分或从已序列化的嵌套并行区域调用,则此函数返回 1。
有关详细信息,请参阅 omp_set_dynamic。
交叉引用
3.1.3 omp_get_max_threads 函数
omp_get_max_threads
函数返回一个整数,如果在代码中的该点看到没有 num_threads
子句的并行区域,则该整数保证至少与用于形成团队的线程数一样大。 格式如下所示:
#include <omp.h>
int omp_get_max_threads(void);
以下内容表示 omp_get_max_threads
值的下限:
threads-used-for-next-team<=
omp_get_max_threads
请注意,如果另一个并行区域使用 num_threads
子句请求特定数量的线程,则对 omp_get_max_threads
结果下限的保证不再成立。
omp_get_max_threads
函数的返回值可用于为在下一个并行区域形成的团队中的所有线程动态分配足够的存储。
交叉引用
3.1.4 omp_get_thread_num 函数
omp_get_thread_num
函数返回其团队内正在执行该函数的线程的线程号。 线程号介于 0 和 omp_get_num_threads()
-1(含)之间。 团队的主线程是线程 0。
格式如下所示:
#include <omp.h>
int omp_get_thread_num(void);
如果从串行区域调用,则 omp_get_thread_num
返回 0。 如果从已序列化的嵌套并行区域调用,则此函数返回 0。
交叉引用
3.1.5 omp_get_num_procs 函数
omp_get_num_procs
函数返回调用函数时程序可用的处理器数。 格式如下所示:
#include <omp.h>
int omp_get_num_procs(void);
3.1.6 omp_in_parallel 函数
如果在并行执行的并行区域的动态范围内调用 omp_in_parallel
函数,则返回一个非零值;否则返回 0。 格式如下所示:
#include <omp.h>
int omp_in_parallel(void);
从并行执行的区域(包括已序列化的嵌套区域)调用时,此函数返回一个非零值。
3.1.7 omp_set_dynamic 函数
omp_set_dynamic
函数启用或禁用可用于执行并行区域的线程数的动态调整。 格式如下所示:
#include <omp.h>
void omp_set_dynamic(int dynamic_threads);
如果 dynamic_threads 的计算结果为非零值,则运行时环境可能会自动调整用于执行即将到来的并行区域的线程数,以充分利用系统资源。 因此,用户指定的线程数是最大线程计数。 执行并行区域的团队中的线程数在该并行区域的持续时间内保持不变,并由 omp_get_num_threads
函数报告。
如果 dynamic_threads 的计算结果为 0,则禁用动态调整。
当从 omp_in_parallel
函数返回零的程序部分调用时,此函数具有上述效果。 如果从 omp_in_parallel
函数返回非零值的程序部分调用,则此函数的行为未定义。
对 omp_set_dynamic
的调用优先于 OMP_DYNAMIC
环境变量。
线程动态调整的默认值由实现定义。 因此,依赖特定线程数才能正确执行的用户代码应显式禁用动态线程。 实现不需要提供动态调整线程数的能力,但需要提供接口以支持跨所有平台的可移植性。
Microsoft 专用
目前对 omp_get_dynamic
和 omp_set_dynamic
的支持如下:
omp_set_dynamic
的输入参数不会影响线程处理策略,也不会更改线程数。 omp_get_num_threads
始终返回用户定义的编号(如果已设置)或默认线程号。 在当前的 Microsoft 实现中,omp_set_dynamic(0)
关闭动态线程处理,以便可以将现有线程集重新用于之后的并行区域。 omp_set_dynamic(1)
通过放弃现有线程集并为即将到来的并行区域创建新集来启用动态线程处理。 新集中的线程数与旧集相同,并且基于 omp_get_num_threads
的返回值。 因此,为了获得最佳性能,请使用 omp_set_dynamic(0)
重用现有线程。
交叉引用
3.1.8 omp_get_dynamic 函数
如果启用线程的动态调整,omp_get_dynamic
函数将返回一个非零值;否则返回 0。 格式如下所示:
#include <omp.h>
int omp_get_dynamic(void);
如果其实现未实现线程数的动态调整,则此函数始终返回 0。 有关详细信息,请参阅 omp_set_dynamic。
交叉引用
- 有关动态线程调整的说明,请参阅 omp_set_dynamic。
3.1.9 omp_set_nested 函数
omp_set_nested
函数启用或禁用嵌套并行。 格式如下所示:
#include <omp.h>
void omp_set_nested(int nested);
如果 nested 的计算结果为 0,则默认禁用嵌套并行,嵌套并行区域由当前线程序列化和执行。 否则,将启用嵌套并行,并且嵌套的并行区域可能会部署额外的线程以形成嵌套团队。
当从 omp_in_parallel
函数返回零的程序部分调用时,此函数具有上述效果。 如果从 omp_in_parallel
函数返回非零值的程序部分调用,则此函数的行为未定义。
此调用优先于 OMP_NESTED
环境变量。
启用嵌套并行后,用于执行嵌套并行区域的线程数由实现定义。 因此,即使启用了嵌套并行,也允许符合 OpenMP 的实现对嵌套并行区域进行序列化。
交叉引用
3.1.10 omp_get_nested 函数
如果启用嵌套并行,omp_get_nested
函数将返回一个非零值;如果禁用嵌套并行,则返回 0。 有关嵌套并行的详细信息,请参阅 omp_set_nested。 格式如下所示:
#include <omp.h>
int omp_get_nested(void);
如果实现未实现嵌套并行,则此函数始终返回 0。
3.2 锁函数
本部分中描述的函数将操作用于同步的锁。
对于以下函数,锁变量必须具有类型 omp_lock_t
。 此变量只能通过这些函数访问。 所有锁函数都需要一个参数,该参数具有指向 omp_lock_t
类型的指针。
- omp_init_lock 函数初始化简单锁。
- omp_destroy_lock 函数移除简单锁。
- omp_set_lock 函数一直等到简单锁可用。
- omp_unset_lock 函数释放简单锁。
- omp_test_lock 函数测试简单锁。
对于以下函数,锁变量必须具有类型 omp_nest_lock_t
。 此变量只能通过这些函数访问。 所有可嵌套锁函数都需要一个参数,该参数具有指向 omp_nest_lock_t
类型的指针。
- omp_init_nest_lock 函数初始化可嵌套锁。
- omp_destroy_nest_lock 函数移除可嵌套锁。
- omp_set_nest_lock 函数一直等到可嵌套锁可用。
- omp_unset_nest_lock 函数释放可嵌套锁。
- omp_test_nest_lock 函数测试可嵌套锁。
OpenMP 锁函数以这样一种方式访问锁变量,即它们始终读取和更新锁变量的最新值。 因此,OpenMP 程序不必包含显式 flush
指令,也能确保锁变量的值在不同线程之间保持一致。 (可能需要 flush
指令使其他变量的值保持一致。)
3.2.1 omp_init_lock 和 omp_init_nest_lock 函数
这些函数提供初始化锁的唯一方法。 每个函数都会初始化与参数 lock 关联的锁,以便在即将到来的调用中使用。 格式如下所示:
#include <omp.h>
void omp_init_lock(omp_lock_t *lock);
void omp_init_nest_lock(omp_nest_lock_t *lock);
初始状态是未锁定(即所有线程都没有锁)。 对于可嵌套锁,初始嵌套计数为零。 使用已初始化的锁变量调用其中任一例程都是不合规的。
3.2.2 omp_destroy_lock 和 omp_destroy_nest_lock 函数
这些函数确保指向的锁变量 lock 未初始化。 格式如下所示:
#include <omp.h>
void omp_destroy_lock(omp_lock_t *lock);
void omp_destroy_nest_lock(omp_nest_lock_t *lock);
使用未初始化或未锁定的锁变量调用其中任一例程都是不合规的。
3.2.3 omp_set_lock 和 omp_set_nest_lock 函数
其中每个函数都会阻止执行函数的线程,直到指定的锁可用,然后设置锁。 如果简单锁未锁定,则可以使用该锁。 如果可嵌套锁未锁定或已由执行函数的线程拥有,则可以使用该锁。 格式如下所示:
#include <omp.h>
void omp_set_lock(omp_lock_t *lock);
void omp_set_nest_lock(omp_nest_lock_t *lock);
对于简单锁,omp_set_lock
函数的参数必须指向已初始化的锁变量。 系统会将锁的所有权授予执行函数的线程。
对于可嵌套锁,omp_set_nest_lock
函数的参数必须指向已初始化的锁变量。 嵌套计数递增,并授予线程或保留锁的所有权。
3.2.4 omp_unset_lock 和 omp_unset_nest_lock 函数
这些函数提供释放锁所有权的方法。 格式如下所示:
#include <omp.h>
void omp_unset_lock(omp_lock_t *lock);
void omp_unset_nest_lock(omp_nest_lock_t *lock);
其中每个函数的参数都必须指向执行该函数的线程拥有的初始化锁变量。 如果线程不拥有该锁,则行为未定义。
对于简单锁,omp_unset_lock
函数会释放执行函数的线程对锁的所有权。
对于可嵌套锁,omp_unset_nest_lock
函数会递减嵌套计数,如果最终计数为零,则释放执行函数的线程对锁的所有权。
3.2.5 omp_test_lock 和 omp_test_nest_lock 函数
这些函数尝试设置锁,但不阻止线程执行。 格式如下所示:
#include <omp.h>
int omp_test_lock(omp_lock_t *lock);
int omp_test_nest_lock(omp_nest_lock_t *lock);
参数必须指向已初始化的锁变量。 这些函数尝试以与 omp_set_lock
和 omp_set_nest_lock
相同的方式设置锁,但它们不会阻止线程的执行。
对于简单锁,如果成功设置锁,omp_test_lock
函数将返回一个非零值;否则返回零。
对于可嵌套锁,如果成功设置锁,omp_test_nest_lock
函数将返回新的嵌套计数;否则返回零。
3.3 计时例程
本部分中描述的函数支持便携式时钟计时器:
- omp_get_wtime 函数返回已用时钟时间。
- omp_get_wtick 函数返回连续时钟计时周期之间的秒数。
3.3.1 omp_get_wtime 函数
omp_get_wtime
函数返回一个双精度浮点值,该值等于自“过去的某个时间”以来已用的时钟时间(以秒为单位)。 实际的“过去的某个时间”是任意的,但保证在应用程序执行过程中不会改变。 格式如下所示:
#include <omp.h>
double omp_get_wtime(void);
预计该函数将用于测量已用时间,如以下示例所示:
double start;
double end;
start = omp_get_wtime();
... work to be timed ...
end = omp_get_wtime();
printf_s("Work took %f sec. time.\n", end-start);
返回的时间是“每线程时间”,这意味着它们不需要在参与应用程序的所有线程之间保持全局一致。
3.3.2 omp_get_wtick 函数
omp_get_wtick
函数返回一个双精度浮点值,该值等于连续时钟计时周期之间的秒数。 格式如下所示:
#include <omp.h>
double omp_get_wtick(void);