如何在C++代码中使用OpenMP群星种族特性代码

OpenMP程序开发
OpenMP程序开发
发布时间: 14:58:48
编辑:www.fx114.net
本篇文章主要介绍了"OpenMP程序开发",主要涉及到OpenMP程序开发方面的内容,对于OpenMP程序开发感兴趣的同学可以参考一下。
OpenMP是一个业界的标准,很早以前就有了,只是近一段时间才逐渐热起来。我们可以在C/C++和Fortran使用OpenMP、很容易的引入多线程。
OpenMP目前的标准是2.5,3.0马上也要通过了。官方网站www.openmp.org上有你需要知道的一切信息,包括OpenMP规范文档。&
下图是一个典型的OpenMP程序的示意图,我们可以看到它是由串行代码和并行代码交错组成的,并行代码的区域我们把它叫做“并行区”。主线程一旦进入并行区,就自动产生出多个线程,来并行的执行。&
怎样在我们的代码中使用OpenMP呢?很简单,拿我们常用的C/C++代码来说,只需要插入如下pragma,然后我们选择不同的construct就可以完成不同的功能。&
首先,我们来看一下如何创建一个并行区:增加一行代码#pragma omp parallel,然后用花括号把你需要放在并行区内的语句括起来,并行区就创建好了。并行区里每个线程都会去执行并行区中的代码。&
那么怎么来控制并行区中线程的数量呢?默认情况下,并行区内线程数=系统中核的个数。如果你需要修改,方法之一是设置环境变量。&
下一节我们将看一个最简单的OpenMP代码,看看并行区是怎样工作的。&
我们来看一个最简单的OpenMP程序例子:&
===================================&
#include &stdio.h&&
int main() {&
& & #pragma omp parallel&
& & & & printf(&Hello World/n&);&
& & & & for(i=0;i &6;i++)&
& & & & & & printf(&Iter:%d/n&,i);&
& & printf(&GoodBye World/n&);&
===================================&
我们现在用Intel编译器来编译这个小程序,当然你也可以用VS2005来编译。&
我们可以看到编译器在没有加 /Qopenmp 开关的时候,会忽略掉所有openmp pragma。我们可以利用这个特性来检查你的代码在串行的时候运行&
结果是否正确。&
D:/test&icl /nologo HelloWorlds.c&
HelloWorlds.c&
HelloWorlds.c(4): warning #161: unrecognized #pragma&
& & & #pragma omp parallel&
& & & & & & & ^&
串行代码执行的结果如下:&
D:/test&HelloWorlds.exe&
Hello World&
GoodBye World&
下面我们加上 /Qopenmp 开关重新编译,我们会看到编译器给出提示说已经把并行区给做并行化了。&
D:/test&icl /nologo /Qopenmp HelloWorlds.c&
HelloWorlds.c&
HelloWorlds.c(4): (col. 5) remark: OpenMP DEFINED REGION WAS PARALLELIZED.&
我的机器是4核,运行这个程序就打印出如下结果。我们可以看到每次运行打印出来的东西顺序是不一定的,这个符合多线程程序的特性。在4&
核的机器上,并行区内就产生了4个线程同时执行for循环打印,默认线程数=核的个数。&
D:/test&HelloWorlds.exe&
Hello World&
Hello World&
Hello World&
Hello World&
GoodBye World&
我们来修改一下线程的数量,然后再运行一下代码,看下运行结果&
D:/test&set OMP_NUM_THREADS=2&
D:/test&HelloWorlds.exe&
Hello World&
Hello World&
GoodBye World&
现在我们对并行区有了一定的了解,并行区里每个线程执行的代码是一样的,做的事情似乎也是一样的,但是实际工作中,我们希望把一部分&
工作分配给不同线程来做,这应该怎么实现呢?
下面我们来看一个新的openmp语句&
#pragma omp for&
使用这个语句,我们就可以把一个for循环的工作量(例如:1...N)分配给不同线程。这个语句后面必须紧跟一个for循环,他只能对循环的工作量进行划分、分配。&
看下面这个例子,0...11的工作量被平均分配给了3个线程。当3个线程都完成了各自的工作后,程序才继续往下执行。&
写2行openmp pragma实在有些麻烦,我们可以把2行或多行openmp pragma合并未一行,这样好多了,是吧。&
==================================&
& 数据环境(Data Environment)&
==================================&
OpenMP属于共享内存的编程模型。在我们的多线程代码中,大部分数据都是可以共享的。共享内存给我们程序中数据的共享带来了极大的便利。因此在默认情况下,OpenMP将全局变量、静态变量设置为共享属性。&
但是,还是有些变量需要是每个线程私有的,也就是每个线程有这些变量的独立拷贝,这样每个线程在使用这些变量时不会相互影响。需要私有的变量包括:&
我们可以通过如下方法来改变OpenMP的变量默认属性,你可以把它设置为共享(shared)或无。也可以单独改变某几个变量的属性,把他们设置为shared或private。&
看看下面这个例子,循环变量i默认为私有,因为x和y是中间变量,应该设置为私有,否则线程之间的x,y会互相影响。&
再看看这个例子。变量sum定义在并行区之外,所以默认为共享,这个例子里又写了shared(sum),没错,但是实际上是罗嗦了。那么这个例子里有什么错误呢?&
如果以前做过多线程开发的话应该能看出来,sum不应该是共享的,但是设置为私有的也不对。我们的做法应该是将sum保护起来,防止多个线程同时对sum进行写操作。我们可以使用OpenMP的临界区来对sum进行保护。&
我们可以给临界区命名,在下面例子中,如果我们不给临界区命名,在任一时刻,只能有一个线程调用consum函数。而我们给临界区命名后,任一时刻可以有 最多2个线程在调用consum函数(1个调用 consum(B, &R1),另一个调用 consum(A, &R2)。这在这2句语句可以同时执行的情况下,我们通过临界区命名来尽可能减少线程等待时间。&
归约(Redunction)是个很有用的功能,可以简化我们的编程,op代表一个操作,list是执行这个操作的一个或多个变量。&
我们再看刚才上面的例子就清楚了。我们对sum这个变量使用归约操作,操作符是+。这样的话,每个线程就会有一个私有的sum变量,当所有线程的计算完成 后,每个线程的私有的sum的值将被用“+”归约成一个总的sum,即 线程1的sum + 线程2的sum + ... + 线程n的sum -& 总的sum,这个总的sum值将被带出并行区并赋给全局的那个sum变量,因此,当这个并行区的代码执行完以后,我们的sum变量的值就是我们期望得到的
值了。&
是不是比前面用临界区的方法要好得多、代码也会快得多呢?&
下图是归约支持的操作符:&
好了,来给大家做个小作业。下面代码是一个串行的求Pi的代码,使用的是积分的办法。请大家把这个代码用OpenMP来做并行化。&
================================================&
& & & & & OpenMP中工作量的划分与调度&
================================================&
前面我看到在使用工作量共享(work-sharing)这种方式的时候,工作量是自动给我们划分好并分配给各个线程的。下面,我们来看看如何来控制工作量的划分与调度。&
如上图所示,工作量的划分与调度有3种方式:&
1、静态:把循环的迭代按照每x次(x=chunk)迭代分为一块,这样你的总工作量就被划分成了n/x块(n为迭代次数、循环次数),然后将这些块按照 轮转法依次分配给各个线程。举个例子:比如我们有100次迭代,x=chunk=4,那么我们的工作就被分为25块,假设我们有2个线程可以做工作,那么 线程1分到的块是1,3,5,7....,25,线程2分到的块是2,4,6,...,24;&
2、动态:迭代分块方法同上,但是工作块被放到一个队列中,每个线程每次拿一块,做好了才能到队列里去拿下一块;&
3、Guided:这个方式是动态方式的改进。在这个方式里,分块的x是不固定的,一开始块的大小(x)比较大,随着剩余工作量的减小,块的大小也随之变小。&
我们总结一下每种方式适合什么样的工作量&
静态方式:比较适合每次迭代的工作量相近(主要指工作所需时间)的情况&
动态方式:比较适合每次迭代的工作量非常不确定的情况&
Guided方式:类似动态方式,但是队列相关的开销会比动态方式小&
下面我们看一个例子:&
#pragma omp parallel for schedule (static, 8)&
& & for( int i = i &= i += 2 )&
& & & if ( TestForPrime(i) )& gPrimesFound++;&
在这个例子中,如果start=3,那第一块就={3,5,7,9,11,13,15,17}&
================================================&
& & & & & 另外几个OpenMP Construct&
================================================&
下面我们来看一看Parallel Section,其实看看下面的图片就知道了,我们可以定义多个section,让这些section并行的执行。下面的例子是我们有足够的线程来同时执行这3个section,如果我们只有2个线程,情况会是怎样呢?
如果只有2个线程,那么肯定得有1个线程要比另一个线程勤劳一点,执行2个section了。&
在使用Parallel Section时需要注意的是,每个section的工作之间应该是相互独立、没有依赖关系的。如果不满足这个要求的话,就不要对他们用并行了。&
下面再介绍2个有可能会用到的OpenMP construct。&
1、single:有时候在并行区里,我们希望有部分代码只能执行一次,也就是说只有一个线程去执行这部分代码。如下面的例 子,ExchangeBoundaries() 这句语句前面我们加上 #pragma omp single ,就保证只有一个线程去执行它。同时在single后面会有一个隐含的障碍(implicit barrier)。我们后面会具体介绍障碍这个概念。&
& & & #pragma omp parallel&
& & & & DoManyThings();&
& & & #pragma omp single&
& & & & {&
& & & & & ExchangeBoundaries();&
& & & & }& // threads wait here for single&
& & & & DoManyMoreThings();&
2、master:master跟single很类似。在下面例子中,只有主线程会去执行 ExchangeBoundaries() 这条语句。但是master没有隐含的障碍,因此如果其他线程遇到 #pragma omp master,就会跳过去,直接执行master后面的语句。&
& & & #pragma omp parallel&
& & & & DoManyThings();&
& & & #pragma omp master&
& & & & {& // if not master skip to next stmt&
& & & & & ExchangeBoundaries();&
& & & & }&
& & & & DoManyMoreThings();&
================================================&
& & & & & & & & & 障 碍&
================================================&
在我们前面介绍过的OpenMP Construct,如parallel, for, single,他们自身都带有隐含的障碍。我们来看一下前面出现过的一个例子:&
在这个例子里面,线程1做0-3的迭代,线程2做4-7的迭代,线程3做8-11的迭代,如果每次迭代的工作量不同,那么线程1、2、3完成他们各自的工 作所需的时间是不同的,也就是说,某个线程可能比另外2个线程提前完成工作,但是这个线程不能继续往下走去执行并行区后面的工作,因为#pragma omp for里面带了隐含的障碍,这个障碍的意思就是说,所有的线程做完了自己的工作后必须在这里等,直到所有的线程都完成了各自的工作,大家才能往下走。汇编
里的memory fence与这个有点神似。&
为什么这些construct要带了个隐含的障碍呢?障碍不是让程序执行速度变慢了吗?因为它怕你程序里面后面的代码对这块代码有依赖关系,如果这块代码的工作没完成就去执行后面的代码,可能会引起错误。&
那如果你后面的代码对这块代码没有依赖,可以用 nowait 来把这个隐含的障碍给去掉。比如:&
C/C++ code
#pragma omp for nowait
C/C++ code
#pragma single nowait
C/C++ code
#pragma omp for schedule(dynamic,1) nowait
for(int i=0; i&n; i++)
a[i] = bigFunc1(i);
#pragma omp for schedule(dynamic,1)
for(int j=0; j&m; j++) b[j] = bigFunc2(j);
================================================&
& & & & & & & & & 介绍几个OpenMP API&
================================================&
在大多数情况下,我们不会用到OpenMP API。一般只有在调试和某些情况下,才需要用到API。&
如果你需要使用 OpenMP API,记得先包含OpenMP头文件&
C/C++ code
#include &omp.h&
最常用的2个API是:&
C/C++ code
int omp_get_thread_num(void);
int omp_get_num_threads(void);
在并行区里调用omp_get_thread_num返回的是当前线程的线程ID,一般是0到(N-1),N是并行区里的总线程数。
在并行区里调用omp_get_num_threads返回的是并行区里的总线程数。&
使用这2个API,我们可以只用 #pragma omp parallel 就实现每个线程完成循环中不同的迭代。比如我们有这样一段代码,怎样在不使用 #pragma omp parallel for 的情况下,改写代码、把工作量划分给各个线程呢?&
C/C++ code
#pragma omp parallel
for(i=0;i&N;i++)
c[i] = a[i] + b[i];
下面再介绍几个数据环境相关的construct。前面我们看过了private, public,现在我们来看另外3个。&
1、firstprivate:变量属性为private,同时每个线程的这个变量的初始值为全局变量的值。比如下面例子中,我们先给全局变量incr设 置了一个值0,然后再并行区,每个线程都有自己的incr,而这些incr的初始值也为0(与并行区之前的全局量incr的值一致)。&
C/C++ code
#pragma omp parallel for firstprivate(incr)
for (I=0;I&=MAX;I++) {
if ((I%2)==0) incr++;
2、lastprivate:当退出并行区时,最后一次迭代内的lastprivate变量的值将被带出并行区赋给全局的同名变量。在下面例子中,i循环的最后一次迭代中的x的值将被赋给全局的x,所以退出并行区后,全局量x被赋值了。&
C/C++ code
void sq2(int n, double *lastterm)
double int
#pragma omp parallel
#pragma omp for lastprivate(x)
for (i = 0; i & i++){
x = a[i]*a[i] + b[i]*b[i];
b[i] = sqrt(x);
lastterm =
3、threadprivate:用于指定某变量为线程私有的全局变量。threadprivate和private的区别是private只在并行区中有效,而threadprivate属性是全局范围内有效的。&
& copyin:把全局变量的值拷贝到各线程中同名的threadprivate变量中去。&
C/C++ code
struct Astruct A;
#pragma omp threadprivate(A)
#pragma omp parallel copyin(A)
do_something_to(&A);
#pragma omp parallel
do_something_else_to(&A);
下面我们看个程序加强理解&
C/C++ code
#include &stdio.h&
int main()
int i, x = 100;
#pragma omp parallel for private(x)
for (i=0; i&8; i++)
printf(&x = %d/n&, x);
printf(&global x = %d/n&, x);
因为我的cpu是4核的,所以默认有4个线程,线程1跑i=0、1,线程2跑i=2、3,线程3跑i=4、5,线程2跑i=6、7,所以运行结果为:&
global x = 100&
下面我们把private换成firstprivate,结果就变成了&
global x = 100&
看出区别了吧,每个线程的x的初值都变成100了。&
如果我们再加上个lastprivate:&
C/C++ code
#include &stdio.h&
int main()
int i, x = 100;
#pragma omp parallel for firstprivate(x) lastprivate(x)
for (i=0; i&8; i++)
printf(&x = %d/n&, x);
printf(&global x = %d/n&, x);
结果如下:&
global x = 113&
全局的x的值最后变成了i=7的x的值,i=7是由线程4来做的。线程4在做完i=6时,私有x=106,做完i=7时,私有x=113,因为是lastprivate,113被带给全局量x了。
本文标题:
本页链接:本帖子已过去太久远了,不再提供回复功能。13792人阅读
OpenMP(1)
实用记事(17)
OpenMP是一种API,用于编写可移植的多线程应用程序,无需程序员进行复杂的线程创建、同步、负载平衡和销毁工作。
使用OpenMP的好处:
& &1)CPU核数扩展性问题
& &2)方便性问题
& &3)可移植性问题
OpenMP指令和库函数介绍:
& &在C/C++中,OpenMP指令使用的格式为:#pragma omp 指令 [子句[子句]…]
& &用法详见OpenMP编程指南。
#pragma omp parallel for
& & & &for(i=0;i&i++)
& & & & & &//没有循环迭代相关的语句,如把图像数组中的RGB值转为灰度值。
对可以以多线程执行的循环的约束:
1)循环变量必须是有符号整型,如果是无符号整型,就无法使用
2)比较操作必须是&,&,&=,&=
3)循环步长必须是整数加或整数减操作,加减的操作必须是一个不变量
4)如果是&,&=,循环变量的值每次迭代时必须增加,否则减小
5)循环内部不允许有能够到达循环之外的跳转语句,也不允许有外部的跳转语句到达循环内部。exit语句例外,goto 和break的跳转范围必须在循环内部,异常处理也必须在循环内部处理
数据相关(以下假设为语句S2与语句S1存在数据相关):
相关的种类(相关不等于循环迭代相关):
1)流相关:S1先写某一存储单元,而后S2又读该单元
2)输出相关:两个语句写同一存储单元
3)反相关:一个语句先读一单元,然后另一语句写该单元
相关产生的方式:
1)S1在循环的一次迭代中访问存储单元L,S2在随后的一次迭代中访问L(是循环迭代相关)
2)S1和S2在同一循环迭代中访问同一存储单元L,但S1的执行在S2之前。(非循环迭代相关)
数据竞争:
& & & 数据竞争可能是由于输出相关引起的,编译器不会进行数据竞争的检测,Intel线程检测器可以检测数据竞争。
用类似于互斥量的机制进行私有化和同步,可以消除数据竞争。
#pragma omp parallel for private(x)
& & & &for(i=0;i&80;i++)
& & & & &x=sin(i);
& & & & &if(x&0.6)x=0.6;
& & & & &printf(&sin(%d)=%f\n&,i,x);&
管理共享数据和私有数据:
private:每个线程都拥有该变量的一个单独的副本,可以私有的访问
& & & & &1)private:说明列表中的每个变量对于每个线程都应该有一个私有副本。这个私有副本用变量的默认值进行初始化
& & & & &2)firstprivate:见13数据的Copy-in 和Copy-out
& & & & &3)lastprivate:见13数据的Copy-in 和Copy-out
& & & & &4)reduction:
& & & & &5)threadprivate:指定由每个线程私有的全局变量
有三种方法声明存储单元为私有:
& & & & &1)使用private,firstprivate,lastprivate,reduction子句
& & & & &2)使用threadprivate
& & & & &3)在循环内声明变量,并且不使用static关键字
shared:所有线程都能够访问该单元,并行区域内使用共享变量时,如果存在写操作,必须对共享变量加以保护
default:并行区中所有变量都是共享的,除下列三种情况下:
& & & & & 1)在parallel for循环中,循环索引时私有的。
& & & & & 2)并行区中的局部变量是私有的
& & & & & 3)所有在private,firstprivate,lastprivate,reduction子句中列出的变量是私有的
循环调度与分块
& & &为了提供一种简单的方法以便能够在多个处理器之间调节工作负载,OpenMP给出了四种调度方案:
static,dynamic,runtime,guided.
& & &默认情况下,OpenMP采用静态平均调度策略,但是可以通过调用schedule(kind[,chunksize])子句提供循环调度信息
如:#pragma omp for schedule (kind[,chunk-size]) & //chunk-size为块大小
guided根据环境变量里的设置来进行对前三种的调度
在windows环境中,可以在”系统属性|高级|环境变量”对话框中进行设置环境变量。
有效地使用归约:
for(k=0;k&100;k++)
& & sum=sum+func(k);
& & &为了完成这种形式的循环计算,其中的操作必须满足算术结合律和交换律,同时sum是共享的,这样循环内部都可以加给这个变量,同时又必须是私有的,以避免在相加时的数据竞争。
reduction子句可以用来有效地合并一个循环中某些关于一个或多个变量的满足结合律的算术归约操作。reduction子句主要用来对一个或多个参数条目指定一个操作符,每个线程将创建参数条目的一个私有拷贝,在区域的结束处,将用私有拷贝的值通过指定的运行符运算,原始的参数条目被运算结果的值更新。
#pragma omp parallel for reduction(+:sum)
for(k=0;k&100;k++)
& & sum=sum+func(k);
降低线程开销:当编译器生成的线程被执行时,循环的迭代将被分配给该线程,在并行区的最后,所有的线程都被挂起,等待共同进入下一个并行区、循环或结构化块。
& & & & & & & 如果并行区域、循环或结构化块是相邻的,那么挂起和恢复线程的开销就是没必要的。
举例如下:
& & & & & & & & #pragma omp parallel //并行区内
& & & & & & & & {
& & & & & & & & & &#pragma omp for // 任务分配for循环
& & & & & & & & & & & & & for(k=0;k&m;k++){
& & & & & & & & & & & & & & & &fun1(k);
& & & & & & & & & & & & & &}
& & & & & & & & & &#pragma omp for
& & & & & & & & & & & & & for(k=0;k&m;k++){
& & & & & & & & & & & & & & & &fun2(k);
& & & & & & & & & & & & & &}
& & & & & & & & }
10.任务分配区:
& & &现实中应用程序的所有性能敏感的部分不是都在一个并行区域内执行,所以OpenMP用任务分配区这种结构来处理非循环代码。
任务分配区可以指导OpenMP编译器和运行时库将应用程序中标示出的结构化块分配到用于执行并行区域的一组线程上。
举例如下:
& & & & & & & #pragma omp parallel //并行区内
& & & & & & & & {
& & & & & & & & & &#pragma omp for // 任务分配for循环
& & & & & & & & & & & & & for(k=0;k&m;k++){
& & & & & & & & & & & & & & & &fun1(k);
& & & & & & & & & & & & & &}
& & & & & & & & & &#pragma omp sections private(y,z)
& & & & & & & & & & &{
& & & & & & & & & & & & & &#pragme omp section//任务分配section
& & & & & & & & & & & & & & & &{y=sectionA(x);}
& & & & & & & & & & & & & &#pragme omp section
& & & & & & & & & & & & & & & &{z=sectionB(x);}
& & & & & & & & & & &} & & & & & & & & &&
& & & & & & & & }
使用Barrier和Nowait:
& & & 栅障(Barrier)是OpenMP用于线程同步的一种方法。线程遇到栅障是必须等待,直到并行区中的所有线程都到达同一点。
注意:在任务分配for循环和任务分配section结构中,我们已经隐含了栅障,在parallel,for,sections,single结构的最后,也会有一个隐式的栅障。
隐式的栅障会使线程等到所有的线程继续完成当前的循环、结构化块或并行区,再继续执行后面的工作。可以使用nowait去掉这个隐式的栅障
去掉隐式栅障,例如:
& & & & & & & & #pragma omp parallel //并行区内
& & & & & & & & {
& & & & & & & & & &#pragma omp for nowait // 任务分配for循环
& & & & & & & & & & & & & for(k=0;k&m;k++){
& & & & & & & & & & & & & & & &fun1(k);
& & & & & & & & & & & & & &}
& & & & & & & & & &#pragma omp sections private(y,z)
& & & & & & & & & & &{
& & & & & & & & & & & & & &#pragme omp section//任务分配section
& & & & & & & & & & & & & & & &{y=sectionA(x);}
& & & & & & & & & & & & & &#pragme omp section
& & & & & & & & & & & & & & & &{z=sectionB(x);}
& & & & & & & & & & &} & & & & & & & & &&
& & & & & & & & }
& & &因为第一个 任务分配for循环和第二个任务分配section代码块之间不存在数据相关。
加上显示栅障,例如:
& & & & & & & & & & & & & & & #pragma omp parallel shared(x,y,z) num_threads(2)//使用的线程数为2
& & & & & & & & & & & & & & & &{
& & & & & & & & & & & & & & & & & &int tid=omp_get_thread_num();
& & & & & & & & & & & & & & & & & &if(tid==0)
& & & & & & & & & & & & & & & & & & & &y=fun1();//第一个线程得到y
& & & & & & & & & & & & & & & & & &else&
& & & & & & & & & & & & & & & & & & & & z=fun2();//第二个线程得到z
& & & & & & & & & & & & & & & & & &#pragma omp barrier //显示加上栅障,保证y和z在使用前已有值
& & & & & & & & & & & & & & & & & &#pragma omp for
& & & & & & & & & & & & & & & & & & & & & &for(k=0;k&100;k++)
& & & & & & & & & & & & & & & & & & & & & & & & & &x[k]=y+z;
& & & & & & & & & & & & & & & &}
单线程和多线程交错执行:
& & & 当开发人员为了减少开销而把并行区设置的很大时,有些代码很可能只执行一次,并且由一个线程执行,这样单线程和多线程需要交错执行
举例如下:
& & & & & & & &#pragma omp parallel //并行区
& & & & & & & {
& & & & & & & & & & int tid=omp_get_thread_num();//每个线程都调用这个函数,得到线程号
& & & & & & & & & & &//这个循环被划分到多个线程上进行
& & & & & & & & & & & #pragma omp for nowait
& & & & & & & & & & & for(k=0;k&100;k++)
& & & & & & & & & & & & & & x[k]=fun1(tid);//这个循环的结束处不存在使所有线程进行同步的隐式栅障
& & & & & & & & & & #pragma omp master
& & & & & & & & & & & y=fn_input_only(); //只有主线程会调用这个函数
& & & & & & & & & & #pragma omp barrier & //添加一个显示的栅障对所有的线程同步,从而确保x[0-99]和y处于就绪状态
& & & & & & & & & & &//这个循环也被划分到多个线程上进行
& & & & & & & & & & #pragma omp for nowait
& & & & & & & & & & & for(k=0;k&100;k++)
& & & & & & & & & & & & &x[k]=y+fn2(x[k]); //这个线程没有栅障,所以不会相互等待
& & & & & & & & & & &//一旦某个线程执行完上面的代码,不需要等待就可以马上执行下面的代码
& & & & & & & & & & &#pragma omp single //注意:single后面意味着有隐式barrier
& & & & & & & & & & &fn_single_print(y);
& & & & & & & & & & & //所有的线程在执行下面的函数前会进行同步
& & & & & & & & & & &#pragma omp master
& & & & & & & & & & &fn_print_array(x);//只有主线程会调用这个函数
& & & & & & & }&
数据的Copy-in 和Copy-out:
& & & 在并行化一个程序的时候,一般都必须考虑如何将私有变量的初值复制进来(Copy-in ),以初始化线程组中各个线程的私有副本。
在并行区的最后,还要将最后一次迭代/结构化块中计算出的私有变量复制出来(Copy-out),复制到主线程中的原始变量中。
firstprivate:使用变量在主线程的值对其在每个线程的对应私有变量进行初始化。一般来说,临时私有变量的初值是未定义的。
lastprivate:可以将最后一次迭代/结构化块中计算出来的私有变量复制出来,复制到主线程对应的变量中,一个变量可以同时用firstprivate和lastprivate来声明。
copyin:将主线程的threadprivate变量的值复制到执行并行区的每个线程的threadprivate变量中。
copyprivate:使用一个私有变量将某一个值从一个成员线程广播到执行并行区的其他线程。该子句可以关联single结构(用于single指令中的指定变量为多个线程的共享变量),在所有的线程都离开该结构中的同步点之前,广播操作就已经完成。
保护共享变量的更新操作:
& & &OpenMP支持critical和atomic编译指导,可以用于保护共享变量的更新,避免数据竞争。包含在某个临界段且由atomic编译指导所标记的代码块可能只由一个线程执行。
例如:#pragma omp critical
& & & & & & & if(max&new_value) max=new_
& & & & &}
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);//返回可用的处理核个数
& &编译OpenMP要需要一个支持OpenMP的编译器和线程安全的运行时库。vs2005的配置属性C/C++语言里提供对OpenMP的支持。
& &编译时假如出现&没有找到vcompd.dll,因此这个应用程序未能启动。重新安装应用程序可能会修复此问题&,
可能的原因是该项目有可能是从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='*'&
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:239430次
积分:3129
积分:3129
排名:第8273名
原创:60篇
转载:68篇
评论:50条
(1)(1)(1)(4)(1)(1)(1)(1)(2)(1)(1)(1)(2)(9)(2)(1)(1)(1)(6)(3)(1)(1)(3)(1)(2)(2)(1)(4)(1)(1)(1)(2)(1)(5)(5)(3)(9)(4)(5)(3)(15)(13)(4)

我要回帖

更多关于 如何使用openmp 的文章

 

随机推荐