OpenMP多线程编程

  • 格式:docx
  • 大小:16.40 KB
  • 文档页数:4

OpenMP多线程编程(一)
前言
总算学完了OpenMP多线程编程的内容,由于在《读者》上看到20岁以后,大脑就开始衰老,所以为了保持住不太牢靠的记忆,我决定重新拾起十年前的习惯:归纳总结~OpenMP简介
OpenMP诞生于1997年,由此看来,已经是十多年的历史了。

目前已经有3.0的版本了,但是我使用的是VS2005自带的2.0版本,所以有些功能还不是很健全,譬如task。

注意其支持的语言主要是Fortran和C语言,所以各位朋友在选择的时候要谨慎处理。

在这里,我想展开想象的翅膀,跑题到Java语言的多线程编程中看看。

据我目前的知识了解到,Java多线程编程主要是通过继承Thread类或实现Runnable接口来实现的。

创建线程是通过new关键字,具体的操作在run()里完成。

整体思想可以理解为:
1.完成一个能实现具体功能的线程类,其中的“具体功能”体现在内部的run()方法中;
2.如果想使用某个线程来完成上述类的具体功能,则需要在相应的位置生成该类的对象从而实现调用。

这个过程是比较标准的OO编程思想。

回到我们的主题里,OpenMP可以理解为在C或Fotran语言里,利用自身的编译指导、库函数以及环境变量,对其功能进行拓展,简单易学。

给个例子,大家自己体会一下,就很清晰了。

1 #include "stdafx.h";
2 #include <omp.h> //OpenMP头文件
3 int _tmain(int argc, _TCHAR* argv[])
4 {
5 printf(&quot;This is a OpenMPtest!\n&quot;); //串行执行
6 printf(&quot;Start...\n&quot;);
7 #pragma omp parallel //生成多个线程,执行并行语句
8 {
9 printf(&quot;Hello world! Thread number is %d\n&quot;,omp_get_thread_num());
10 } //并行执行结束
11 printf(&quot;The end!\n&quot;);
12 return 0;
13 }
其中比较陌生的地方自然就是OpenMP的内容了:其中第7行是编译指导;第9行是库函数调用,为此我们引入了omp.h;其中具体有几个线程被生成,没有提到,这个是通过环境变量设置或默认的。

编译环境选择及搭建
结合当前比较火热的编译器,我们选择VS2005,大材小用一次。

我们只利用其中C++的控制台程序。

但是由于其强大的库函数已包括OpenMP,所以无须自己添加环境,只需要修改配置即可。

在编译之前通过菜单项目—>OpenMPtest属性,选择配置属性|C/C++|语言,将OpenMP支持一项,修改为:是(/openmp)
基本编译指导语句
在OpenMP中,创建线程的编译指导语句格式如下:
#pragma omp parallel [clause[clause]…]
{
//并行区域代码
}
只有有了这句话,才能体现出“多线程”。

而且,可以看出,该语法不是C或Fotran 的语法,所以是独立的语句,如果对于不支持OpenMP的编译器,该语句也不会报错,会被当作注释忽略掉而维持单线程的执行模式,其优势不言而喻。

由于是创建了多个线程执行相同的事情,可以理解是多个线程做重复的工作,而很多情况下,我们需要的是任务分配,每个线程做不同的工作。

于是,有下面的语句可以完成。

#pragma omp parallel for [clause[clause]…]
for( index=first; test_ loop_variable;change_loop_variable)
{
//循环体语句
}
注意,只是多了个for,之后的内容就是分解完成。

对于第一个语句,可以理解为,同学们做作业,每人都需要完成;后者可以理解大家合作做项目,每个人只需要完成一个模块,最后集体完成一个项目即可。

以上内容就已经帮我们创建多线程,以及任务分配了。

我们需要在具体的需求下做具体的选择。

先说到这儿,下次说说数据冲突,就是由于这些并行(多线程)而带来的后患,我们应该怎么处理~~~
OpenMP多线程编程(二)
数据相关及解决
串行程序改为并行程序,最容易出现的新问题,就是数据相关与冲突了。

本来由一个线程做的工作,分给多个线程做,自然会出现一些千丝万缕的瓜葛,如同一个人做的项目,现在由几个人来做,难免需要额外沟通和解释的。

数据相关的解决技术在OpenMP也已经比较完善了。

结合OS的思想,数据冲突主要利用临界区和信号量来解决,另外OpenMP增加了数据规约。

下面我们分别谈谈各自使用方法。

临界区
万不得已的选择,因为它是并行程序中不得不串行的区域集合。

所以,我们追求“小”,一个临界区可以拆分的话,尽量拆分。

临界区的工作原理,是需要排队,然后依次执行的,所以效率很低。

具体语句为:#pragma omp critical
例子:累加求和。

对于和的结果sum变量是共享的,所以需要临界区保护,但是效率已经降低为串行。

数据规约
由于临界区的效率比较低,不能实现真正意义上的并行,而对于某些问题是可以通
过分解的思路进行并行的。

比如,上面的累加求和,是可以通过分别累加,然后再汇总的思路完成的,于是出现了数据规约的思想。

语法:#pragma omp for reduction(+:sum)
上述语句就是将sum变量在每个线程中生成一个副本,计算各自线程中累加的结果,然后最后累加到共享的sum变量中。

效率明显提高了吧?不过,这种用法是有局限性的,大家可以体会到写法上涉及到操作,所以,该用法只能用于基本运算符中的操作,譬如加减乘除之类的。

原子操作
微型临界区。

大家可能都听说过原子性,原子操作就是为了将读写绑定到一起,从而避免与更新有关的冲突,由于该“串行工作量”非常小,所以说原子操作提供了一种更高效率的互斥锁机制。

#pragma omp atomic
同样,原子操作也是只能用于特定的原子运算符:+,-,*,/,|,&,^,<<,>>.
注意:以上语句一般都是在parallel语句内部使用,因为主要是由并发引出的一系列数据冲突问题,所以要在并行代码内解决这些冲突~
给大家补充一个例子吧:
#include "stdafx.h"
#include <stdio.h>
#include <time.h>
#include <omp.h>
long long num_steps = 1000000000;
double step;
int _tmain(int argc, _TCHAR* argv[])
{
/*并行执行计算*/
sum=0.0;
start = clock();
#pragma omp parallel for reduction(+:sum),private(x)/*并行控制语句:另外,对sum变量进行数据规约;对x变量进行私有化*/
for (i=0; i<num_steps; i++)
{
x = (i + .5)*step;
sum += 4.0/(1.+ x*x);
}
pi = sum*step;
stop = clock();
printf("The value of PI is %15.12f\n",pi);
printf("并行:The time to calculate PI was %f seconds\n",((double)(stop - start)/1000.0));
return 0;
}。