MPI是可以针对分布式内存,在进程级别实现并行的API;OpenMP则是针对共享内存,在线程级别实现并行的API。
基本使用
不同于MPI的init和finalize,OpenMP用携带了parallel指令的预处理指令指示接下来的一个代码块被多个线程执行。
OpenMP预处理指令-(携带)->OpenMP指令-(携带)->OpenMP指令的子句。
隐式路障是OpenMP的一个特点,所有执行这个代码块的线程在同一个线程组中以隐式路障隐式同步。
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 //检查是否定义了_OPENMP预处理宏,即编译器是否支持OpenMP
5 #ifdef _OPENMP
6 #include<omp.h> //支持时包含OpenMP头文件
7 #endif
8
9 int main(int argc,char *argv[])
10 {
11 //stdlib的strtol()函数把字符串转换成整形数
12 //参数1:字符串首地址,跳过前面的空格符,遇到数字或正负符号才开始做转换
13 //参数2:用于引用返回遇到非数字或'\0'结束转换时的字符指针所在地址
14 //参数3:转换至几进制,取值2~36或0,取0时即默认为10进制
15 int thrdCnt=strtol(argv[1],NULL,0);
16 printf("strtol()函数从主函数参数解析得的线程数:%d\n",thrdCnt);
17 //# pragma omp指示后面接的是OpenMP的预处理指令
18 //parallel指令表明之后的结构化代码块被多个线程执行
19 //num_threads(线程数)子句指定执行后代码块的线程数
20 # pragma omp parallel num_threads(thrdCnt)
21 {
22 //如果不支持OpenMP,前面没有包含omp.h,则里面的函数也不可用
23 //所以要在所有使用了omp.h的代码处做包含检查
24 # ifdef _OPENMP
25 //omp_get_thread_num()函数获得该线程的编号
26 int myThrdNm=omp_get_thread_num();
27 //omp_get_num_threads()函数获得总的线程数
28 int allNmThrds=omp_get_num_threads();
29 # else
30 //当不能使用OpenMP时,只有一个线程,线程号是0
31 int myThrdNm=0;
32 //当不能使用OpenMP时,只有一个线程,线程数目是1
33 int allNmThrds=1;
34 # endif
35 //为了验证"隐式路障",此处sleep()不同的时间,不妨按线程编号延时
36 sleep(myThrdNm*3);//C语言里sleep()的单位是秒
37 printf("我是%d/%d\n",myThrdNm,allNmThrds);
38 }//隐式路障:完成代码块的线程要等待其它所有线程完成此代码块
39 printf("路障解除了\n");
40 return 0;
41 }
输出
1 [lzh@hostlzh OpenMP]$ !gcc
2 gcc -fopenmp -o hello.o hello.c
3 [lzh@hostlzh OpenMP]$ ./hello.o 4
4 strtol()函数从主函数参数解析得的线程数:4
5 我是0/4
6 我是1/4
7 我是2/4
8 我是3/4
9 路障解除了
10 [lzh@hostlzh OpenMP]$
critical指令
OpenMP里变量的作用域可以按照块内是否共享分为共享作用域和私有作用域。对于并行代码块而言,在块前声明的变量缺省作用域是共享的(不过这个”共享”当然不能穿透函数)。
某些地方未使用critical时可能存在的问题
当块内共同操作了共享的资源时,不对其做互斥保护就可能会在运行时出问题。因为只能保证一次汇编级的指令是原子的,甚至不能保证一条C语言语句在并发执行过程中不会发生线程的切换,在并行的情况下就更加危险了:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<omp.h>
4
5 int main(int argc,char *argv[])
6 {
7 int a=20;
8 int thrdCnt=strtol(argv[1],NULL,10);
9 # pragma omp parallel num_threads(thrdCnt)
10 a=a-1;//并行块中访问了共享变量a
11 printf("a=%d\n",a);//最终输出看一下
12 return 0;
13 }
输出
1 [lzh@hostlzh OpenMP]$ !gcc
2 gcc -fopenmp -o test1.o test1.c
3 [lzh@hostlzh OpenMP]$ ./test1.o 15
4 a=5
5 [lzh@hostlzh OpenMP]$ ./test1.o 15
6 a=5
7 [lzh@hostlzh OpenMP]$ ./test1.o 15
8 a=5
9 [lzh@hostlzh OpenMP]$ ./test1.o 15
10 a=5
11 [lzh@hostlzh OpenMP]$ ./test1.o 15
12 a=5
13 [lzh@hostlzh OpenMP]$ ./test1.o 15
14 a=6
15 [lzh@hostlzh OpenMP]$ ./test1.o 15
16 a=5
17 [lzh@hostlzh OpenMP]$ ./test1.o 15
18 a=5
才执行了这么几次就发生了出错,可见问题严重。
使用critical时
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<omp.h>
4
5 int main(int argc,char *argv[])
6 {
7 int a=20;
8 int b=20;
9 int thrdCnt=strtol(argv[1],NULL,10);
10 # pragma omp parallel num_threads(thrdCnt)
11 {
12 //OpenMP指令critical安排线程对接下来的代码块互斥访问
13 # pragma omp critical
14 a=a-1;//共享变量a受critical保护互斥访问
15 b=b-1;//共享变量b未受critical保护
16 }
17 printf("a=%d\nb=%d\n",a,b);//最终输出看一下
18 return 0;
19 }
输出
1 [lzh@hostlzh OpenMP]$ !gcc
2 gcc -fopenmp -o test1.o test1.c
3 [lzh@hostlzh OpenMP]$ ./test1.o 15
4 a=5
5 b=5
6 [lzh@hostlzh OpenMP]$ ./test1.o 15
7 a=5
8 b=5
9 [lzh@hostlzh OpenMP]$ ./test1.o 15
10 a=5
11 b=5
12 [lzh@hostlzh OpenMP]$ ./test1.o 15
13 a=5
14 b=5
15 [lzh@hostlzh OpenMP]$ ./test1.o 15
16 a=5
17 b=5
18 [lzh@hostlzh OpenMP]$ ./test1.o 15
19 a=5
20 b=5
21 [lzh@hostlzh OpenMP]$ ./test1.o 15
22 a=5
23 b=5
24 [lzh@hostlzh OpenMP]$ ./test1.o 15
25 a=5
26 b=5
27 [lzh@hostlzh OpenMP]$ ./test1.o 15
28 a=5
29 b=5
30 [lzh@hostlzh OpenMP]$ ./test1.o 15
31 a=5
32 b=5
33 [lzh@hostlzh OpenMP]$ ./test1.o 15
34 a=5
35 b=5
36 [lzh@hostlzh OpenMP]$ ./test1.o 15
37 a=5
38 b=5
39 [lzh@hostlzh OpenMP]$ ./test1.o 15
40 a=5
41 b=5
42 [lzh@hostlzh OpenMP]$ ./test1.o 15
43 a=5
44 b=6
45 [lzh@hostlzh OpenMP]$ ./test1.o 15
46 a=5
47 b=5
48 [lzh@hostlzh OpenMP]$ ./test1.o 15
49 a=5
50 b=5
51 [lzh@hostlzh OpenMP]$ ./test1.o 15
52 a=5
53 b=5
54 [lzh@hostlzh OpenMP]$
可见受critical指令保护的a能够正常执行了,而不受保护的b仍然会发生前面的问题。