1.
OpenMP是一种API,用于编写可移植的多线程应用程序,无需程序员进行复杂的线程创建、同步、负载平衡和销毁工作。
使用OpenMP的好处:
OpenMP指令和库函数介绍:
2.
#pragma omp parallel for
3.
对可以以多线程执行的循环的约束:
1)循环变量必须是有符号整型,如果是无符号整型,就无法使用
2)比较操作必须是<,>,<=,>=
3)循环步长必须是整数加或整数减操作,加减的操作必须是一个不变量
4)如果是<,<=,循环变量的值每次迭代时必须增加,否则减小
5)循环内部不允许有能够到达循环之外的跳转语句,也不允许有外部的跳转语句到达循环内部。exit语句例外,goto 和break的跳转范围必须在循环内部,异常处理也必须在循环内部处理
4.
数据相关(以下假设为语句S2与语句S1存在数据相关):
相关的种类(相关不等于循环迭代相关):
1)流相关:S1先写某一存储单元,而后S2又读该单元
2)输出相关:两个语句写同一存储单元
3)反相关:一个语句先读一单元,然后另一语句写该单元
相关产生的方式:
1)S1在循环的一次迭代中访问存储单元L,S2在随后的一次迭代中访问L(是循环迭代相关)
2)S1和S2在同一循环迭代中访问同一存储单元L,但S1的执行在S2之前。(非循环迭代相关)
5.
数据竞争:
用类似于互斥量的机制进行私有化和同步,可以消除数据竞争。
#pragma omp parallel for private(x)
6.
管理共享数据和私有数据:
private:每个线程都拥有该变量的一个单独的副本,可以私有的访问
有三种方法声明存储单元为私有:
shared:所有线程都能够访问该单元,并行区域内使用共享变量时,如果存在写操作,必须对共享变量加以保护
default:并行区中所有变量都是共享的,除下列三种情况下:
7.
循环调度与分块
static,dynamic,runtime,guided.
如:#pragma omp for schedule (kind[,chunk-size])
guided根据环境变量里的设置来进行对前三种的调度
在windows环境中,可以在”系统属性|高级|环境变量”对话框中进行设置环境变量。
8.
有效地使用归约:
sum=0;
for(k=0;k<100;k++)
{
}
reduction子句可以用来有效地合并一个循环中某些关于一个或多个变量的满足结合律的算术归约操作。reduction子句主要用来对一个或多个参数条目指定一个操作符,每个线程将创建参数条目的一个私有拷贝,在区域的结束处,将用私有拷贝的值通过指定的运行符运算,原始的参数条目被运算结果的值更新。
sum=0;
#pragma omp parallel for reduction(+:sum)
for(k=0;k<100;k++)
{
}
9.
降低线程开销:当编译器生成的线程被执行时,循环的迭代将被分配给该线程,在并行区的最后,所有的线程都被挂起,等待共同进入下一个并行区、循环或结构化块。
举例如下:
10.任务分配区:
任务分配区可以指导OpenMP编译器和运行时库将应用程序中标示出的结构化块分配到用于执行并行区域的一组线程上。
举例如下:
11.
使用Barrier和Nowait:
注意:在任务分配for循环和任务分配section结构中,我们已经隐含了栅障,在parallel,for,sections,single结构的最后,也会有一个隐式的栅障。
隐式的栅障会使线程等到所有的线程继续完成当前的循环、结构化块或并行区,再继续执行后面的工作。可以使用nowait去掉这个隐式的栅障
去掉隐式栅障,例如:
加上显示栅障,例如:
12.
单线程和多线程交错执行:
举例如下:
13.
数据的Copy-in 和Copy-out:
在并行区的最后,还要将最后一次迭代/结构化块中计算出的私有变量复制出来(Copy-out),复制到主线程中的原始变量中。
firstprivate:使用变量在主线程的值对其在每个线程的对应私有变量进行初始化。一般来说,临时私有变量的初值是未定义的。
lastprivate:可以将最后一次迭代/结构化块中计算出来的私有变量复制出来,复制到主线程对应的变量中,一个变量可以同时用firstprivate和lastprivate来声明。
copyin:将主线程的threadprivate变量的值复制到执行并行区的每个线程的threadprivate变量中。
copyprivate:使用一个私有变量将某一个值从一个成员线程广播到执行并行区的其他线程。该子句可以关联single结构(用于single指令中的指定变量为多个线程的共享变量),在所有的线程都离开该结构中的同步点之前,广播操作就已经完成。
14.
保护共享变量的更新操作:
例如:#pragma omp critical
{
15.
OpenMP库函数(#include <omp.h>):
int omp_get_num_threads(void); //获取当前使用的线程个数
int omp_set_num_threads(int NumThreads);//设置要使用的线程个数
int omp_get_thread_num(void);//返回当前线程号
int omp_get_num_procs(void);//返回可用的处理核个数
16.
可能的原因是该项目有可能是从VC移植过来的,如果由VS创建,一般不会出现该问题,因为VS会解决在清单文件的调用dll问题。
解决方法如下:
StdAfx.h中加入 #pragma comment(linker, "\"/manifestdependency:type='Win32' name='Microsoft.VC80.DebugOpenMP' version='8.0.50608.0' processorArchitecture='X86' publicKeyToken='1fc8b3b9a1e18e3b' language='*'\"")
或者在Linker -> Manifest File -> Additional Manifest Dependencies -> 中加入:
"type='Win32' name='Microsoft.VC80.DebugOpenMP' version='8.0.50608.0' processorArchitecture='X86' publicKeyToken='1fc8b3b9a1e18e3b' language='*'"
<!--[if !supportLists]-->1 <!--[endif]-->简介
GNU的gomp项目;
Include <omp.h>;编译参数-fopenmp打开openmp,如果使用-lgomp则编译成单线程版本;
条件编译 #ifdef _OPENMP
声明并行区,由编译器和运行时库确定线程的创建、终止、调度和数量,并行区结束后线程挂起等待进入下一并行区;
Intel编译器的OMP开关,关上时将编译成非并行代码,-Qopenmp
<!--[if !supportLists]-->2 <!--[endif]-->将for并行化
#pragma omp parallel for
<!--[if !supportLists]-->1, <!--[endif]-->比较操作不能为!=和==;
<!--[if !supportLists]-->2, <!--[endif]-->步长必须为循环不变量;
<!--[if !supportLists]-->3, <!--[endif]-->单入口单出口,不能有break和goto
循环多线程化需要避免“迭代相关”,要求循环执行的次序可以被任意打乱和组织,而不必安装常规的循环变量依次变化的方式执行;
循环并行化的一种方式是:把循环均分成n段,每段由一个线程并行执行;
消除迭代相关的一种方法:手动对循环分段,再使用section指令,每段一个section:
#pragma omp section
<!--[if !supportLists]-->3 <!--[endif]-->Private子句
将被多线程共享,而逻辑上独立的变量使用private;private变量会在每个线程中保存一个副本,并使用默认值初始化;
int x;
#pragma omp parallel for private(x)
for(…){
x = …
}
上面也可以通过将x声明到循环体内解决
<!--[if !supportLists]-->4 <!--[endif]-->Schedule子句控制调度
schedule(kind[,chunksize])
<!--[if !supportLists]-->l <!--[endif]-->static调度策略,循环被近乎均分的方式分配给各线程,一般不指定块大小;
<!--[if !supportLists]-->l <!--[endif]-->dynamic,使用内部任务队列,线程执行完被分配的块后再从队列中取新的任务执行;
<!--[if !supportLists]-->l <!--[endif]-->guided,与dynamic类似,但先取较大块后逐渐减小,以减少访问内部队列的次数;
<!--[if !supportLists]-->l <!--[endif]-->runtime,运行时通过环境变量确定具体调度策略,如:
export OMP_SCHEDULE=dynamic,16
<!--[if !supportLists]-->5 <!--[endif]-->reduction子句
sum = 0
#pragma omp parallel for reduction(+:sum)
for(…)
sum = sum + fun(k);
这样编译器生成每个线程的sum副本,并按一定规则附相应初始值,循环完成时再将它们合并到原始的sum变量;类似“+”的操作还可以有-/*/&/等;
<!--[if !supportLists]-->6 <!--[endif]-->Barrier栅障——线程同步
For循环与section结构中隐含使用了栅障,nowait子句去除它
#pragma omp for nowait
#pragma omp barrier 显示使用栅障
<!--[if !supportLists]-->7 <!--[endif]-->并行区中的串行区
只由一个线程执行一次
#pragma omp master 只由主线程执行
#pragma omp single 只由一个线程执行一次
<!--[if !supportLists]-->8 <!--[endif]-->临界区与原子操作
#pragma omp critical
#pragma omp critical(name) 命名临界区,这样该临界区就可以在多处被引用了
#pragma omp atomic 只能是简单操作
<!--[if !supportLists]-->9 <!--[endif]-->函数
需要#include <omp.h>,不建议使用;
omp_set_num_threads(n)
omp_get_thread_num() 线程编号
omp_get_num_procs() CPU数量
<!--[if !supportLists]-->10 <!--[endif]-->If子句
#pragma omp parallel for if(n>100) n>100时才执行并行化
<!--[if !supportLists]-->11 <!--[endif]-->环境变量
OMP_SCHEDULE
OMP_NUM_THREADS 默认线程数