用以同步的编译器指令
在多个线程并发的时候,某一线程常常会需要同步其它线程。OpenMP支持多种类型的同步,以在不同的情境下解决问题。
其中之一就是暗含的barrier同步。在每一个并行区域都有一个暗含的barrier,用以同步并行区域中的所有线程。一个barrier同步要求所有线程执行到此,然后才能往下执行。
#pragma omp for,#pragma omp single和#pragma omp sections程序块都有暗含的barrier同步。从上述三种工作共享的程序块中去除暗含的barrier同步的方法是增加nowait子句:
#pragma omp parallel
{
#pragma omp for nowait
for(int i = 1; i < size; ++i)
x[i] = (y[i-1] + y[i+1])/2;
}
如你所见,工作共享指令中的nowait子句指明线程不需要在for循环结束时同步,尽管线程将在并行区域结束处同步。
另一种是明确声明barrier同步,在一些情境下你可能需要在并行区域出口之外放置barrier同步,这时你可以在代码里加一个#pragma omp barrier指令。
临界区能够像barrier那样使用,在Win32 API中通过EnterCriticalSection和ExitCriticleSection来进出临界区。OpenMP通过#pragma omp critical [name]指令给予程序员同样的能力。这与Win32临界区有同样的语义,并且隐藏了EnterCriticleSection的调用。你可以使用命名的临界区,这种情况下代码段仅与同名临界区互斥。如果没有指定临界区名字,则映射到用户未定义的名字。这些未命名的临界区与区域相关的每一临界区互斥。
在一个并行区域里,经常限制同时只有一条线程能够访问一段代码,例如在并行区域的中间写文件。大多数这种情况下,并不关心哪一条线程执行这段代码,只要只有一条线程执行这段代码即可,OpenMP用#pragma omp single指令来完成这个工作。
有此时候用single指令声明必须由单一线程执行并行区域中的一段代码并不满足需要。有些情况下你希望确保主线程来执行这段代码——例如主线程是GUI线程并且你希望GUI线程完成一些工作。#pragma omp master指令可以做到这一点。不像single,在进出一个master代码块的时候并没有暗含的barrier。
内存界定(Memory Fence)可用#pragma omp flush实现,这条指令在程序中生成内存界定,它的本质上等效于_ReadWriteBarrier。
切记OpenMP指令同时影响线程组里的所有线程。因此下面的代码片段是非法的并且有未定义的运行时行为(崩溃或者在特别情况下被挂起):
#pragma omp parallel
{
if(omp_get_thread_num() > 3)
{
#pragma omp single // May not be accessed by all threads
x++;
}
}
执行环境例程
除了前文讨论的编译器指令OpenMP也包含一系列极为有用的运行时例程,用以编写OpenMP应用程序。有三大类型的例程可用:执行环境例程,锁/同步例程和定时例程(定时例程不在本文讨论)。所有的OpenMP例程都在omp.h头文件中定义并皆以omp_开头。
运行时环境例程提供允许你查询和设置OpenMP环境的各个方面的功能。以omp_set_开头的函数只能在并行区域外调用,其它函数可在并行和非并行区域使用。
可以用omp_get_num_threads和omp_set_num_threads来读取或者设置线程组的线程数量。omp_get_num_threads返回当前线程组的线程数目。如果调用此函数的线程不在并行区域,返回1。omp_set_num_threads用以设置当前线程执行下一个并行区域的线程数。
但这并非设置线程数目的全部,并行区域的线程数目同样依赖于OpenMP的另两方面的配置环境:动态线程和嵌套。
动态线程是一个默认为不使能的布尔属性。当线程将执行一块并行区域的时候如果这个属性为不使能,那么OpenMP就生线程数量为omp_get_max_threads返回值的线程组。omp_get_max_threads默认为计算机的硬件线程数或者环境变量OMP_NUM_THREADS的值。如果使能动态线程OpenMP将生成一个线程数量可变的线程组,但这个数量不会超过omp_get_max_threads的返回值。
嵌套是另一个默认为不使能的布尔属性。并行区域嵌套出现在当线程已经运行在并行区域又遇到另一个并行区域的时候。如果嵌套被使能,那么就按前文关于动态线程的规则生成一个新的线程组。相反地,线程组就只有单独一个线程。
可以通过omp_set_dynamic、omp_get_dynamic、omp_set_nested和 omp_get_nested来设置或者查询动态线程和嵌套的使能状态。每一条线程都可以查询它所处的环境。线程可以通过调用omp_get_thread_num来获得它所处的线程组的线程数目——一个比调用omp_get_num_threads的返回值少0或者1的值。
omp_in_parallel用以查询本线程是否正在并行区域执行。omp_get_num_proc用以获知计算机有多少个CPU。