outline•OpenMP•一些并行程序设计问题的解决办法OpenMP•提供一种快捷简单的多线程编程方法•形成于1997年,是一种应用程序接口•对编程人员:–无需进行复杂的线程创建、同步、负载平衡和销毁工作。–只需要认真考虑哪些循环应该以多线程方式运行。–Intel编译器提供对OpenMP的支持。–···•概述•OpenMP应用编程接口API是在共享存储体系结构上的一个编程模型•包含编译制导(CompilerDirective)、运行库例程(RuntimeLibrary)和环境变量(EnvironmentVariables)•支持增量并行化(IncrementalParallelization)OpenMP•结合了两种并行编程的方式–编译指导语句,在编译过程并行化代码–运行时库函数,在运行时对并行环境支持•OpenMP应用程序的组成部分编译指导语句运行时函数库环境变量什么是OpenMP•什么是OpenMP–应用编程接口API(ApplicationProgrammingInterface)–由三个基本API部分(编译指令、运行部分和环境变量)构成–是C/C++和Fortan等的应用编程接口–已经被大多数计算机硬件和软件厂家所标准化•OpenMP不包含的性质–不是建立在分布式存储系统上的–不是在所有的环境下都是一样的–不是能保证让多数共享存储器均能有效的利用OpenMP的历史•1994年,第一个ANSIX3H5草案提出,被否决•1997年,OpenMP标准规范代替原先被否决的ANSIX3H5,被人们认可•1997年10月公布了与Fortran语言捆绑的第一个标准规范FORTRANversion1.0•1998年11月9日公布了支持C和C++的标准规范C/C++version1.0•2000年11月推出FORTRANversion2.0•2002年3月推出C/C++version2.0•2005年5月OpenMP2.5将原来的Fortran和C/C++标准规范相结合•相关的规范可在中下载使用VisualStudio2005编写OpenMP程序•当前的VisualStudio.Net2005完全支持OpenMP2.0标准,通过新的编译器选项/openmp来支持OpenMP程序的编译和链接使用VisualStudio2005编写OpenMP程序(续)#include“stdafx.h”#include“omp.h”int_tmain(intargc,_TCHAR*argv[]){printf(“Hellofromserial.\n”);printf(“Threadnumber=%d\n”,omp_get_thread_num());//串行执行#pragmaompparallel//开始并行执行{printf(“Hellofromparallel.Threadnumber=%d\n”,omp_get_thread_num());}printf(“Hellofromserialagain.\n”);return0;}使用VisualStudio2005编写OpenMP程序(续2)•OpenMP程序使用到的环境变量OMP_NUM_THREADS设置为4•默认的情况下–OMP_NUM_THREADS=系统中逻辑CPU的数目–在双核的系统中OMP_NUM_THREADS=2–双核超线程的逻辑CPU的数目为4,•三次执行的结果在MicrosoftVisualStudio.Net2005环境下面编写OpenMP程序的必要步骤•1)生成Console项目;•2)配置项目,使之支持OpenMP;•3)编写代码,加入#include“omp.h”;•4)编写源程序;•5)配置环境变量OMP_NUM_THREADS,确定线程数目;•6)执行程序。OpenMP的目标•标准性•简洁实用•使用方便•可移植性OpenMP并行编程模型•基于线程的并行编程模型(ProgrammingModel)•OpenMP使用Fork-Join并行执行模型FORKJOINFORKJOIN主线程并行域并行域OpenMP多线程编程基础•以线程为基础,通过编译指导语句来显式地指导并行化,为编程人员提供了对并行化的完整的控制。•采用Fork-Join的形式MasterThreadParalllRegionNestedParallelRegionfork-Join执行模式•在开始执行的时候,只有主线程的运行线程存在•主线程在运行过程中,当遇到需要进行并行计算的时候,派生出(Fork,创建新线程或者唤醒已有线程)线程来执行并行任务•在并行执行的时候,主线程和派生线程共同工作•在并行代码结束执行后,派生线程退出或者挂起,不再工作,控制流程回到单独的主线程中(Join,即多线程的会和)。编译制导OpenMP的#pragma语句的格式为#pragmaompdirective_name…#pragmaompdirective-name[clause,...]newline制导指令前缀。对所有的OpenMP语句都需要这样的前缀。OpenMP制导指令。在制导指令前缀和子句之间必须有一个正确的OpenMP制导指令。子句。在没有其它约束条件下,子句可以无序,也可以任意的选择。这一部分也可以没有。换行符。表明这条制导语句的终止。编译指导语句•在编译器编译程序的时候,会识别特定的注释•这些注释就包含着OpenMP程序的一些语义#pragmaompdirective[clause[[,]clause]…]其中directive部分就包含了具体的编译指导语句,包括parallel,for,parallelfor,section,sections,single,master,critical,flush,ordered和atomic。•在无法识别OpenMP语义的普通编译器中,这些注释被忽略•将串行的程序逐步地改造成一个并行程序编译制导•作用域–静态扩展•文本代码在一个编译制导语句之后,被封装到一个结构块中–孤立语句•一个OpenMP的编译制导语句不依赖于其它的语句–动态扩展•包括静态范围和孤立语句作用域动态范围静态范围for语句出现在一个封闭的并行域中孤立语句critical和sections语句出现在封闭的并行域之外#pragmaompparallel{…#pragmaompforfor(…){…sub1();…}…sub2();…}voidsub1(){…#pragmaompcritical…}voidsub2(){…#pragmaompsections…}并行域结构•并行域中的代码被所有的线程执行•具体格式–#pragmaompparallel[clause[[,]clause]…]newline–clause=•if(scalar-expression)•private(list)•firstprivate(list)•default(shared|none)•shared(list)•copyin(list)•reduction(operator:list)•num_threads(integer-expression)共享任务结构•共享任务结构将它所包含的代码划分给线程组的各成员来执行–并行for循环–并行sections–串行执行FORKFORKFORKJOINJOINJOIN主线程主线程线程列主线程主线程线程列主线程主线程线程列SECTIONSSINGLEDO/forloopfor编译制导语句•for语句指定紧随它的循环语句必须由线程组并行执行;•语句格式–#pragmaompfor[clause[[,]clause]…]newline–[clause]=•Schedule(type[,chunk])•ordered•private(list)•firstprivate(list)•lastprivate(list)•shared(list)•reduction(operator:list)•nowaitfor编译制导语句•schedule子句描述如何将循环的迭代划分给线程组中的线程•如果没有指定chunk大小,迭代会尽可能的平均分配给每个线程•type为static,循环被分成大小为chunk的块,静态分配给线程•type为dynamic,循环被动态划分为大小为chunk的块,动态分配给线程循环并行化语句的限制•循环并行化的语句必须具有如下的形式for(index=start;indexend;increment_expr)–index必须是一个整数–小于号()也可以被其它的比较操作符替代–start和end可以是任意的数值表达式,但是在循环的过程中其值不能改变,以保证能够在循环之前就计算出循环的次数。–increment_expr形式如下,其中incr是一个在循环过程中不变的数值表达式•循环语句块应该是单出口与单入口的,在循环过程中不能使用break、goto和return语句•可以使用continue语句,因为这个语句不影响循环执行的次数。操作符incrementt_expr的形式++index++或者++index--index--或者--index+=index+=incr-=index-=incr=index=index+incr或者index=incr+index或者index=index-incr简单循环并行化•将两个向量相加,并将计算的结果保存到第三个向量中,向量的维数为nfor(inti=0;in;i++)z[i]=x[i]+y[i];•各个分量之间没有数据相关性•循环计算的过程也没有循环依赖性•程序改成#pragmaompparallelforfor(inti=0;in;i++)z[i]=x[i]+y[i];循环并行化编译指导语句的子句•循环并行化子句可以包含一个或多个子句来控制循环并行化的执行–有多个类型的子句可以用来控制循环并行化编译–最主要的子句是数据作用域子句。•由于有多线程同时执行循环语句中的功能指令,这就涉及到数据的作用域问题–作用域用来控制某一个变量是否是在各个线程之间共享或者是某一个线程是私有的–数据的作用域子句用shared来表示一个变量是各个线程之间共享的–用private来表示一个变量是每一个线程私有的–默认的变量作用域是共享的•其他编译指导子句–用来控制线程的调度(schedule子句)–动态控制是否并行化(if子句)–进行同步的子句(ordered子句)–控制变量在串行部分与并行部分传递的子句(copyin子句)循环嵌套•循环并行化编译指导语句可以加在任意一个循环之前•对应的最近的循环语句被并行化,其它部分保持不变inti;intj#pragmaompparallelforprivate(j)for(i=0;i2;i++)for(j=6;j10;j++)printf(“i=%dj=%d\n”,i,j);执行结果:i=0j=6i=1j=6i=0j=7i=1j=7i=0j=8i=1j=8i=1j=9i=0j=9inti;intj;for(i=0;i2;i++)#pragmaompparallelforfor(j=6;j10;j++)printf(i=%dj=%d\n,i,j);执行结果:i=0j=6i=0j=8i=0j=9i=0j=7i=1j=6i=1j=8i=1j=7i=1j=9Sections编译制导语句•sections编译制导语句指定内部的代码被划分给线程组中的各线程•不同的section由不同的线程执行•Section语句格式:#pragmaompsections[clause[[,]clause]…]newline{[#pragmaompsectionnewline]…[#pragmaompsectionnewline]…}Sections编译制导语句•clause=–pr