并行计算-openMP学习笔记(2)

时间:2022-06-24 18:36:59

        在正式编写并行程序前,需要先了解一下共享任务结构。所谓共享任务结构,即将结构中所包含的代码分给线程组中各个线程去执行。第一个线程为0,也即主线程,在共享任务结构的入口没有路障,而出口(即结束处)有一个隐含的路障,只有线程0可以从头执行到尾。

        OpenMP有3种典型的共享任务结构,for 语句、sections 语句和 single 语句,此外还有 parallel for 语句和 parallel sections 语句。

1. for语句

for语句的一般格式如下:([ ]表示可选可不选,| 表示或者)
#pragma omp for [schedule(static|dynamic[,CHUNKSIZE]) | ordered | private(var) | firstprivate(var) | 
lastprivate(var) | shared(var) | reduction(operator:var) | nowait] newline
最后的newline指换行,具体可以看一下例子
#include<stdio.h>
#include<omp.h>
#include<time.h>

#define CHUNKSIZE 100
#define N 10000
int main(){
int i;
float a[N], b[N], c[N];
clock_t t=0;
for (i = 0; i < N; i++)
a[i] = b[i] = i*1.0;
t -= clock();
#pragma omp parallel shared(a,b,c) private(i) //一定要换行,即newline的意思
{
#pragma omp for schedule(static,CHUNKSIZE) //for语句只能作用于一个for循环,for循环可以嵌套,但不能用{}套住for循环
for (i = 0; i < N; i++)
c[i] = a[i] + b[i];
}
t += clock();
printf("时间:%d\n", t);
return 0;
}
上面用到了schedule子句,作用是描述如何将循环划分给线程组中的线程,若没有schedule子句,则循环将尽可能平均地划分给不同的线程;schedule有一个必填参数和一个可选参数,必填参数的选项是static和dynamic,前者在编译时就已经分配好各个线程的任务大小,这从static这个词的意思可以理解,后者是在程序运行时根据线程的闲置来动态地分配任务;可选参数指各个任务的大小,当第一个参数为static时,CHUNSIZE不填默认平均分配,当第一个参数为dynamic时,CHUNSIZE不填默认块长为1。

2. sections语句

嵌套在sections语句中的每个独立section的编译制导语句将由一个线程单独执行,sections语句的一般格式为
#pragma omp sections [private(var) | firstprivate(var) | lastprivate(var) | reduction(operator:var) | nowait] newline
{
[#pragma omp section newline]
......
[#pragma omp section newline]
......
}
这里需要注意的是,除非使用nowait,在sections语句结束处有一个隐含的路障。
#include<stdio.h>
#include<omp.h>
#include<time.h>

#define N 1000
int main(){
int i;
float a[N], b[N], c[N];
for (i = 0; i < N; i++)
a[i] = b[i] = i*1.0;
clock_t t = 0;
t -= clock();
#pragma omp parallel shared(a,b,c) private(i) //并行域中程序只由两个线程执行
{
#pragma omp sections nowait //使用nowait消除路障
{
#pragma omp section
for (i = 0; i < N / 2; i++)
c[i] = a[i] + b[i];
#pragma omp section
for (i = N / 2; i < N; i++)
c[i] = a[i] + b[i];
} //这里有一个隐含的路障,线程组中先执行完的线程将等到上面代码块执行结束才继续执行
}
t += clock();
printf("时间:%d\n", t);
return 0;
}

3. single语句

single语句指定内部代码只由一个线程执行,一般用于非线程安全的操作(输入/输出),一般格式如下:
#pragma omp single [private(var) | firstprivate(var) | nowait] newline
除非使用nowait语句,否则single将阻塞其它线程的执行知道single代码块执行完毕
具体可看下面的例子
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<omp.h>
#include<time.h>

#define N 1000
int main(){
int tid,i;
float a[N], b[N], c[N];
for (i = 0; i < N; i++)
a[i] = b[i] = i*1.0;
omp_set_num_threads(4);
#pragma omp parallel private(tid,i)
{
clock_t t = 0;
t -= clock();
tid = omp_get_thread_num();
#pragma omp single //可以添加nowait试一下有什么不同
{
int c;
scanf("%d", &c);
printf("成功阻塞其它线程的执行\n");
}
for (i = 0; i < N; i++)
c[i] = a[i] + b[i];
t += clock();
printf("线程%d的执行时间为%d\n",tid,t);
}
return 0;
}

4. parallel for语句

parallel for的一般格式如下:
#pragma omp parallel for [if(scalar_logical_expression) | default(chared|none) | schedule(static|dynamic[,CHUNKSIZE]) | 
shared(var) | private(var) | firstprivate(var) | lastprivate(var) | reduction(operator:var) | copyin(var)] newline
这个语句用的比较多。看一个例子
#include<stdio.h>
#include<omp.h>
#include<time.h>

#define CHUNKSIZE 100
#define N 1000
int main()
{
int i;
float a[N], b[N], c[N];
for (i = 0; i < N; i++)
a[i] = b[i] = i*1.0;
clock_t t = 0;
//omp_set_num_threads(32);
t -= clock();
#pragma omp parallel for shared(a,b,c) private(i) schedule(static,CHUNKSIZE) //没有指定线程数量时,默认为系统拥有的CPU核数
for (i = 0; i < N; i++)
c[i] = a[i] + b[i];
t += clock();
printf("时间:%d\n",t);
return 0;
}

5. parallel sections语句

该编译制导语句表示一个并行域包含单独的一个sections语句,一般格式如下:
#pragma omp parallel sections [default(chared|none) | shared(var) | private(var) | 
firstprivate(var) | lastprivate(var) | reduction(operator:var) | copyin(var) | ordered] newline
看下面这个例子
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<omp.h>
#include<time.h>

#define CHUNKSIZE 100
#define N 1000
int main()
{
int i;
float a[N], b[N], c[N];
for (i = 0; i < N; i++)
a[i] = b[i] = i*1.0;
clock_t t = 0;
omp_set_num_threads(4);
t -= clock();
#pragma omp parallel sections shared(a,b,c) private(i)
{
for (i = 0; i < N; i++)
c[i] = a[i] + b[i];
}
t += clock();
printf("时间:%d\n", t);
return 0;
}
被这个语句套住的代码块只能被一个线程执行。