1. 执行模式
概述:
OpenMP是跨平台的多核多线程编程的一套指导性的编译处理方案(Compiler Directive),指导编译器将代码编译为多线程程序。
Openmp的执行模型采用fork-join的形式,其中fork(派生)创建新线程或者唤醒已有线程;join即多线程的会合。
OpenMP的编程者需要在可并行工作的代码部分用制导指令向编译器指出其并行属性。(并行区域可以出现嵌套的情况)。
并行域与线程:
- 对**并行域(Paralle region)**作如下定义:在成对的fork和join之间的区域,称为并行域,它既表示代码也表示执行时间区间。
- 对OpenMP线程作如下定义:在OpenMP程序中用于完成计算任务的一个执行流的执行实体,可以是操作系统的线程也可以是操作系统上的进程。
2. OpenMP编程要素
OpenMP编程模型以线程为基础,通过编译制导指令来显式地指导并行化,OpenMP为编程人员提供了三种编程要素来实现对并行化的完善控制。它们是编译制导、API函数集和环境变量。
2.1编译制导
C/C++程序中,OpenMP的所有编译制导指令是以#pragma omp
开始,后面跟具体的功能指令(或命令),其具有如下形式:
#pragma omp 指令 子句
支持OpenMP的编译器能识别、处理这些制导指令并实现其功能。其中指令或命令是可以单独出现的,而子句则必须出现在制导指令之后。制导指令和子句按照功能可以大体上分成四类:
- 并行域控制类;
- 任务分担类;
- 同步控制类;
- 数据环境类。
OpenMP规范中的指令:
- parallel:用在一个结构块之前,表示这段代码将被多个线程并行执行;
- for:用于for循环语句之前,表示将循环计算任务分配到多个线程中并行执行,以实现任务分担,必须由编程人员自己保证每次循环之间无数据相关性;
- sections:用在可被并行执行的代码段之前,用于实现多个结构块语句的任务分担,可并行执行的代码段各自用section指令标出(注意区分sections和section);
- single:用在并行域内,表示一段只被单个线程执行的代码;
- critical:用在一段代码临界区之前,保证每次只有一个OpenMP线程进入;
- flush、barrier、atomic、master、threadprivate …
相应的OpenMP子句:
- private:指定一个或多个变量在每个线程中都有它自己的私有副本;
- firstprivate:指定一个或多个变量在每个线程都有它自己的私有副本,并且私有变量要在进入并行域或任务分担域时,继承主线程中的同名变量的值作为初值;
- lastprivate:是用来指定将线程中的一个或多个私有变量的值在并行处理结束后复制到主线程中的同名变量中,负责拷贝的线程是for或sections任务分担中的最后一个线程;
- reduction:用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的归约运算,并将结果返回给主线程同名变量;
- nowait:指出并发线程可以忽略其他制导指令暗含的路障同步;
- num_threads:指定并行域内的线程的数目;
- schedule、shared、ordered、copyprivate、copyin、default…
2.2 API函数
除上述编译制导指令之外,OpenMP还提供了一组API函数用于控制并发线程的某些行为,下面列出OpenMP 2.5所有的API函数:
2.3 环境变量
OpenMP规范定义了一些环境变量,可以在一定程度上控制OpenMP程序的行为。以下是开发过程中常用的环境变量
OMP_SCHEDULE
:用于for循环并行化后的调度,它的值就是循环调度的类型;OMP_NUM_THREADS
:用于设置并行域中的线程数;OMP_DYNAMIC
:通过设定变量值,来确定是否允许动态设定并行域内的线程数;OMP_NESTED
:指出是否可以并行嵌套。
编译方式
C / C++编译语句添加:-fopenmp
3. 并行域管理
parallel制导语句:开辟并行域,也可组合使用形成复合指令 由线程组并行执行
例:开辟并行域
#include <stdio.h>
#include <omp.h>
int main()
{
#pragma omp parallel
{
printf("hello world! from thread_num %d\n", omp_get_thread_num());
}
return 0;
}
输出:
hello world! from thread_num 5
hello world! from thread_num 4
hello world! from thread_num 8
hello world! from thread_num 6
hello world! from thread_num 2
hello world! from thread_num 13
hello world! from thread_num 3
hello world! from thread_num 19
hello world! from thread_num 16
hello world! from thread_num 12
hello world! from thread_num 7
hello world! from thread_num 1
hello world! from thread_num 11
hello world! from thread_num 14
hello world! from thread_num 9
hello world! from thread_num 10
hello world! from thread_num 15
hello world! from thread_num 18
hello world! from thread_num 17
hello world! from thread_num 0
可以通过omp_set_num_threads(n);
设置使用的线程数
#include <stdio.h>
#include <omp.h>
int main()
{
omp_set_num_threads(2);
#pragma omp parallel
{
printf("hello world! from thread_num %d\n", omp_get_thread_num());
}
return 0;
}
输出:
hello world! from thread_num 0
hello world! from thread_num 1
4. 任务分担
当使用parellel制导指令产生出并行域之后,如果仅仅是多个线程执行完全相同的任务,那么只是徒增计算工作量而不能达到加速计算的目的,甚至可能相互干扰得出错误结果。因此在产生出并行域之后,紧接着的问题就是如何将计算任务在这些线程之间分配,并加快计算结果的生成速度及其保证正确性。
OpenMP可以完成的任务分担的指令只有for
、sections
和single
。
4.1 for制导指令
for制导语句:指定紧随它的循环语句由线程组并行执行
例:for制导语句
#include <stdio.h>
#include <omp.h>
int main()
{
#pragma omp parallel
{
int i;
#pragma omp for
for (i = 0; i < 4; i++)
printf("i = %d,from thread_num%d\n", i, omp_get_thread_num());
}
return 0;
}
写法2: parallel for
开辟for并行域
#include <stdio.h>
#include <omp.h>
int main()
{
int i;
#pragma omp parallel for
for (i = 0; i < 4; i++)
printf("i = %d,from thread_num%d\n", i, omp_get_thread_num());
return 0;
}
这里我们对第一层循环并行执行,第二层循环非并行执行:
#include <stdio.h>
#include <omp.h>
int main()
{
int i, j;
#pragma omp parallel for
for (i = 0; i < 4; i++)
printf("i = %d,from thread_num%d\n", i, omp_get_thread_num());
puts("");
for (j = 0; j < 4; j++)
printf("j = %d,from thread_num%d\n", j, omp_get_thread_num(