OpenMP

介绍

OpenMP是一个跨平台的多线程实现，主线程(顺序的执行指令)生成一系列的子线程，并将任务划分给这些子线程进行执行。这些子线程并行的运行，由运行时环境将线程分配给不同的处理器。
要进行并行执行的代码片段需要进行相应的标记，用预编译指令使得在代码片段被执行前生成线程，每个线程会分配一个id，可以通过函数(called omp_get_thread_num())来获得该值，该值是一个整数，主线程的id为0。在并行化的代码运行结束后，子线程join到主线程中，并继续执行程序。
默认情况下，各个线程独立地执行并行区域的代码。可以使用Work-sharing constructs来划分任务，使每个线程执行其分配部分的代码。通过这种方式，使用OpenMP可以实现任务并行和数据并行。
运行时环境分配给每个处理器的线程数取决于使用方法、机器负载和其他因素。线程的数目可以通过环境变量或者代码中的函数来指定。在C/C++中，OpenMP的函数都声明在头文件omp.h中。

历史

OpenMP Architecture Review Board (ARB)于1997年10月发布了OpenMP for Fortran 1.0。次年的10月，发布了C/C++的标准。2000年，发布了Fortran语言的2.0版本，并于2002年发布了C/C++语言的2.0版本。2005年，包含Fortran和C/C++的2.5版本发布了。
在2008年5月发布了3.0版。3.0中的新功能包括任务(tasks)和任务结构(task construct)的概念。这些新功能总结在OpenMP3.0规范的附录F中。
OpenMP规范的3.1版于2011年7月9日发布。
4.0版本在2013年7月发布，它增加或改进以下功能：支持加速器，原子性，错误处理，线程关联，任务扩展，减少用户定义的SIMD支持和Fortran 2003的支持。

特色

OpenMP提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度，这样程序员可以把更多的精力投入到并行算法本身，而非其具体实现细节。对基于数据分集的多线程程序设计，OpenMP是一个很好的选择。同时，使用OpenMP也提供了更强的灵活性，可以较容易的适应不同的并行系统配置。线程粒度和负载平衡等是传统多线程程序设计中的难题，但在OpenMP中，OpenMP库从程序员手中接管了部分这两方面的工作。

语法

#pragma omp <directive> [clause[[,] clause] ...]

OpenMP的库函数

OpenMP定义了20多个库函数： 
1.void omp_set_num_threads(int _Num_threads);
在后续并行区域设置线程数，此调用只影响调用线程所遇到的同一级或内部嵌套级别的后续并行区域.说明：此函数只能在串行代码部分调用.
2.int omp_get_num_threads(void);
返回当前线程数目.说明：如果在串行代码中调用此函数，返回值为1.
3.int omp_get_max_threads(void);
如果在程序中此处遇到未使用num_threads() 子句指定的活动并行区域,则返回程序的最大可用线程数量.说明：可以在串行或并行区域调用，通常这个最大数量由omp_set_num_threads()或OMP_NUM_THREADS环境变量决定.
4.int omp_get_thread_num(void);
返回当前线程id.id从1开始顺序编号,主线程id是0.
5.int omp_get_num_procs(void);
返回程序可用的处理器数.
6.void omp_set_dynamic(int _Dynamic_threads);
启用或禁用可用线程数的动态调整.(缺省情况下启用动态调整.)此调用只影响调用线程所遇到的同一级或内部嵌套级别的后续并行区域.如果 _Dynamic_threads 的值为非零值,启用动态调整;否则,禁用动态调整.
7.int omp_get_dynamic(void);
确定在程序中此处是否启用了动态线程调整.启用了动态线程调整时返回非零值;否则,返回零值.
8.int omp_in_parallel(void);
确定线程是否在并行区域的动态范围内执行.如果在活动并行区域的动态范围内调用,则返回非零值;否则,返回零值.活动并行区域是指IF 子句求值为TRUE 的并行区域.
9.void omp_set_nested(int _Nested);
启用或禁用嵌套并行操作.此调用只影响调用线程所遇到的同一级或内部嵌套级别的后续并行区域._Nested 的值为非零值时启用嵌套并行操作;否则,禁用嵌套并行操作.缺省情况下,禁用嵌套并行操作.
10.int omp_get_nested(void);
确定在程序中此处是否启用了嵌套并行操作.启用嵌套并行操作时返回非零值;否则,返回零值.
互斥锁操作
嵌套锁操作
功能
11.void omp_init_lock(omp_lock_t * _Lock);      
12. void omp_init_nest_lock(omp_nest_lock_t * _Lock);
初始化一个（嵌套）互斥锁.
13.void omp_destroy_lock(omp_lock_t * _Lock);    
14.void omp_destroy_nest_lock(omp_nest_lock_t * _Lock);
结束一个（嵌套）互斥锁的使用并释放内存.
15.void omp_set_lock(omp_lock_t * _Lock);        
16.void omp_set_nest_lock(omp_nest_lock_t * _Lock);
获得一个（嵌套）互斥锁.
17.void omp_unset_lock(omp_lock_t * _Lock);      
18.void omp_unset_nest_lock(omp_nest_lock_t * _Lock); 
释放一个（嵌套）互斥锁.
19.int omp_test_lock(omp_lock_t * _Lock);        
20.int omp_test_nest_lock(omp_nest_lock_t * _Lock);
试图获得一个（嵌套）互斥锁,并在成功时放回真（true）,失败是返回假（false）.
21.double omp_get_wtime(void);
获取wall clock time,返回一个double的数,表示从过去的某一时刻经历的时间,一般用于成对出现,进行时间比较.
此函数得到的时间是相对于线程的,也就是每一个线程都有自己的时间.
22.double omp_get_wtick(void);
得到clock ticks的秒数.

例子

在 omp parallel 段内的程序代码由多线程来运行：
 int main(int argc, char* argv[])
 {
  #pragma omp parallel  
   printf("Hello, world.\n");

   return 1;
 }

运行结果

% gcc omp.c （由單線程來執行）
% ./a.out
Hello, world.

% gcc -fopenmp omp.c （由多線程來執行）
% ./a.out
Hello, world.
Hello, world.
Hello, world.
Hello, world.

环境变量

OpenMP可以使用环境变量 OMP_NUM_THREADS以控制运行线程的数量。

例子

% gcc -fopenmp omp.c 

% setenv OMP_NUM_THREADS 2（由2線程來執行）
setenv是CSH的指令

在bash shell 環境中 要用export 
% export OMP_NUM_THREADS=2 （由2線程來執行）

% ./a.out
Hello, world.
Hello, world.

争议

作为高层抽象，OpenMP并不适合需要复杂的线程间同步和互斥的场合。 OpenMP的另一个缺点是不能在非共享内存系统（如计算机集群）上使用。在这样的系统上，MPI使用较多。

编译器支持

主流C/C++编译器，如gcc与visual C++，都内在支持OpenMP。一般都必须在程序中#include <omp.h>
gcc编译时需使用编译选项-fopenmp。但是，如果编译为目标文件与链接生成可执行文件是分开为两步操作，那么链接时需要给出附加库gomp，否则会在链接时报错“undefined reference to `omp_get_thread_num'"。
Visual C++需要在IDE的编译选项->语言->支持OpenMP。这实际上使用了编译选项/openmp