section语句是用在sections语句里用来将sections语句里的代码划分成几个不同的段
第一部分:基础部分转载于:http://www.cnblogs.com/yangyangcv/archive/2012/03/23/2413335.html
openMP支持的编程语言包括C语言、C++和Fortran,支持OpenMP的编译器包括Sun Studio,Intel Compiler,Microsoft Visual Studio,GCC。我使用的是Microsoft Visual Studio 2008,CPU为Intel i5 四核,首先讲一下在Microsoft Visual Studio 2008上openMP的配置。非常简单,总共分2步:
(1) 新建一个工程。这个不再多讲。
(2) 建立工程后,点击 菜单栏->Project->Properties,弹出菜单里,点击 Configuration Properties->C/C++->Language->OpenMP Support,在下拉菜单里选择Yes。
至此配置结束。下面我们通过一个小例子来说明openMP的易用性。这个例子是 有一个简单的test()函数,然后在main()里,用一个for循环把这个test()函数跑8遍。
1 #include
2 #include
3 void test()
4 {
5 int a = 0;
6 for (int i=0;i<100000000;i++)
7 a++;
8 }
9 int main()
10 {
11 clock_t t1 = clock();
12 for (int i=0;i<8;i++)
13 test();
14 clock_t t2 = clock();
15 std::cout<<"time: "<16 }
编译运行后,打印出来的耗时为:1.971秒。下面我们用一句话把上面代码变成多核运行。
1 #include
2 #include
3 void test()
4 {
5 int a = 0;
6 for (int i=0;i<100000000;i++)
7 a++;
8 }
9 int main()
10 {
11 clock_t t1 = clock();
12 #pragma omp parallel for
13 for (int i=0;i<8;i++)
14 test();
15 clock_t t2 = clock();
16 std::cout<<"time: "<17 }
编译运行后,打印出来的耗时为:0.546秒,几乎为上面时间的1/4。
由此我们可以看到openMP的简单易用。在上面的代码里,我们一没有额外include头文件,二没有额外link库文件,只是在for循环前加了一句#pragma omp parallel for。而且这段代码在单核机器上,或者编译器没有将openMP设为Yes的机器上编译也不会报错,将自动忽略#pragma这行代码,然后按照传统单核串行的方式编译运行!我们唯一要多做的一步,是从C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.OPENMP和C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\Debug_NonRedist\x86\Microsoft.VC90.DebugOpenMP目录下分别拷贝vcomp90d.dll和vcomp90.dll文件到工程文件当前目录下。
对上面代码按照我的理解做个简单的剖析。
当编译器发现#pragma omp parallel for后,自动将下面的for循环分成N份,(N为电脑CPU核数),然后把每份指派给一个核去执行,而且多核之间为并行执行。下面的代码验证了这种分析。
1 #include
2 int main()
3 {
4 #pragma omp parallel for
5 for (int i=0;i<10;i++)
6 std::cout<7 return 0;
8 }
会发现控制台打印出了0 3 4 5 8 9 6 7 1 2。注意:因为每个核之间是并行执行,所以每次执行时打印出的顺序可能都是不一样的。
下面我们来了谈谈竞态条件(race condition)的问题,这是所有多线程编程最棘手的问题。该问题可表述为,当多个线程并行执行时,有可能多个线程同时对某变量进行了读写操作,从而导致不可预知的结果。比如下面的例子,对于包含10个整型元素的数组a,我们用for循环求它各元素之和,并将结果保存在变量sum里。
1 #include
2 int main()
3 {
4 int sum = 0;
5 int a[10] = {1,2,3,4,5,6,7,8,9,10};
6 #pragma omp parallel for
7 for (int i=0;i<10;i++)
8 sum = sum + a[i];
9 std::cout<<"sum: "<10 return 0;
11 }
如果我们注释掉#pragma omp parallel for,让程序先按照传统串行的方式执行,很明显,sum = 55。但按照并行方式执行后,sum则会变成其他值,比如在某次运行过程中,sum = 49。其原因是,当某线程A执行sum = sum + a[i]的同时,另一线程B正好在更新sum,而此时A还在用旧的sum做累加,于是出现了错误。
那么用openMP怎么实现并行数组求和呢?下面我们先给出一个基本的解决方案。该方案的思想是,首先生成一个数组sumArray,其长度为并行执行的线程的个数(默认情况下,该个数等于CPU的核数),在for循环里,让各个线程更新自己线程对应的sumArray里的元素,最后再将sumArray里的元素累加到sum里,代码如下
1 #include
2 #include
3 int main(){
4 int sum = 0;
5 int a[10] = {1,2,3,4,5,6,7,8,9,10};
6 int coreNum = omp_get_num_procs();//获得处理器个数
7 int* sumArray = new int[coreNum];//对应处理器个数,先生成一个数组
8 for (int i=0;i//将数组各元素初始化为0
9 sumArray[i] = 0;
10 #pragma omp parallel for
11 for (int i=0;i<10;i++)
12 {
13 int k = omp_get_thread_num();//获得每个线程的ID
14 sumArray[k] = sumArray[k]+a[i];
15 }
16 for (int i = 0;i17 sum = sum + sumArray[i];
18 std::cout<<"sum: "<19 return 0;
20 }
需要注意的是,在上面代码里,我们用omp_get_num_procs()函数来获取处理器个数,用omp_get_thread_num()函数来获得每个线程的ID,为了使用这两个函数,我们需要include
上面的代码虽然达到了目的,但它产生了较多的额外操作,比如要先生成数组sumArray,最后还要用一个for循环将它的各元素累加起来,有没有更简便的方式呢?答案是有,openMP为我们提供了另一个工具,归约(reduction),见下面代码:
1 #include
2 int main(){
3 int sum = 0;
4 int a[10] = {1,2,3,4,5,6,7,8,9,10};
5 #pragma omp parallel for reduction(+:sum)
6 for (int i=0;i<10;i++)
7 sum = sum + a[i];
8 std::cout<<"sum: "<9 return 0;
10 }
上面代码里,我们在#pragma omp parallel for 后面加上了 reduction(+:sum),它的意思是告诉编译器:下面的for循环你要分成多个线程跑,但每个线程都要保存变量sum的拷贝,循环结束后,所有线程把自己的sum累加起来作为最后的输出。
reduction虽然很方便,但它只支持一些基本操作,比如+,-,*,&,|,&&,||等。有些情况下,我们既要避免race condition,但涉及到的操作又超出了reduction的能力范围,应该怎么办呢?这就要用到openMP的另一个工具,critical。来看下面的例子,该例中我们求数组a的最大值,将结果保存在max里。
1 #include
2 int main(){
3 int max = 0;
4 int a[10] = {11,2,33,49,113,20,321,250,689,16};
5 #pragma omp parallel for
6 for (int i=0;i<10;i++)
7 {
8 int temp = a[i];
9 #pragma omp critical
10 {
11 if (temp > max)
12 max = temp;
13 }
14 }
15 std::cout<<"max: "<16 return 0;
17 }
上例中,for循环还是被自动分成N份来并行执行,但我们用#pragma omp critical将 if (temp > max) max = temp 括了起来,它的意思是:各个线程还是并行执行for里面的语句,但当你们执行到critical里面时,要注意有没有其他线程正在里面执行,如果有的话,要等其他线程执行完再进去执行。这样就避免了race condition问题,但显而易见,它的执行速度会变低,因为可能存在线程等待的情况。
第二部分转载于:http://www.cnblogs.com/wzyj/p/4501348.html
section语句是用在sections语句里用来将sections语句里的代码划分成几个不同的段
#include
#include
#include
#include
int main()
{
printf("parent threadid:%d\n",omp_get_thread_num());
#pragma omp sections
{
#pragma omp section
{
printf("section 0,threadid=%d\n",omp_get_thread_num());
sleep(1);
}
#pragma omp section
{
printf("section 1,threadid=%d\n",omp_get_thread_num());
//sleep(1);
}
#pragma omp section
{
printf("section 2,threadid=%d\n",omp_get_thread_num());
sleep(1);
}
}
#pragma omp parallel sections
{
#pragma omp section
{
printf("section 3,threadid=%d\n",omp_get_thread_num());
sleep(1);
}
#pragma omp section
{
printf("section 4,threadid=%d\n",omp_get_thread_num());
sleep(1);
}
#pragma omp section
{
printf("section 5,threadid=%d\n",omp_get_thread_num());
sleep(1);
}
}
return 0;
}
输出结果为:
parent threadid:0
section 0,threadid=0
section 1,threadid=0
section 2,threadid=0
section 3,threadid=0
section 4,threadid=2
section 5,threadid=1
针对上面的代码,首先应该明确下面几点:
1. sections之间是串行的。主线程把section0~2执行完之后才执行的第二个sections。
2.没有加parallel的sections里面的section是串行的,为此我专门sleep了一秒,结果显示0~2都是主线程做的。
3.第二个sections里面是并行的,进程编号分别为0,2,1。
问题来了,第二部分的0是不是主线程呢?还是第二部分新开的一个线程?为此需要真正输出每个线程在内核中的线程编号:
#include
#include
#include
#include
#include
#include
int main()
{
printf("pid:%d,tid=%ld\n",getpid(),syscall(SYS_gettid));
#pragma omp sections
{
#pragma omp section
{
printf("section 0,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
#pragma omp section
{
printf("section 1,tid=%ld\n",syscall(SYS_gettid));
//sleep(1);
}
#pragma omp section
{
printf("section 2,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
}
#pragma omp parallel sections
{
#pragma omp section
{
printf("section 3,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
#pragma omp section
{
printf("section 4,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
#pragma omp section
{
printf("section 5,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
}
return 0;
}
输出结果:
pid:7619,tid=7619
section 0,tid=7619
section 1,tid=7619
section 2,tid=7619
section 5,tid=7621
section 4,tid=7619
section 3,tid=7620
从结果中可以看出以下几点:
下面是一个比较复杂的例子
#include
#include
#include
#include
#include
#include
int main()
{
#pragma omp parallel
{
printf("pid:%d,tid=%ld\n",getpid(),syscall(SYS_gettid));
#pragma omp sections
{
#pragma omp section
{
printf("section 0,tid=%ld\n",syscall(SYS_gettid));
//sleep(1);
}
#pragma omp section
{
printf("section 1,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
#pragma omp section
{
printf("section 2,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
}
#pragma omp sections
{
#pragma omp section
{
printf("section 3,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
#pragma omp section
{
printf("section 4,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
#pragma omp section
{
printf("section 5,tid=%ld\n",syscall(SYS_gettid));
sleep(1);
}
}
}
return 0;
}
输出结果:
pid:7660,tid=7660 section 0,tid=7660 section 1,tid=7660 pid:7660,tid=7662 section 2,tid=7662 pid:7660,tid=7663 pid:7660,tid=7661 section 3,tid=7660 section 5,tid=7661 section 4,tid=7662
#pragma omp parallel里面的代码是并行处理的,但是并不意味着代码要执行N次(其中N为核数),sections之间是串行的,而并行的实际部分是sections内部的代码。当线程7660在处理0,1时,因为section1休眠1s,所以section2在此期间会被新的线程进行处理。第一个sections真正处理完成之后,第二个sections才开始并行处理。
另外值得注意的是,printf并不是并行的函数,它是将结果输出到控制台中,可是控制台资源并不是共享的。当被某个线程占用之后,其余的线程只能等待,拿输出的结果为例。对于#pragma omp parallel里面的代码是并行的,可是线程之间还是有先后的次序的,次序和线程的创建时间有关,对于线程7660来说,本身就已经存在了,所以首先获得printf函数,而直到它执行section0里面的printf时,其他的线程还没有创建完毕,接着是setion1里面的printf,即使是这个时候有其他的线程创建完成了,也只能等待,在section1中,sleep了1秒钟,printf函数被新的线程使用,下面也如此。