开一篇博客,并试图逼自己入门简单的openmp并行计算
头文件:#include <omp.h>
omp parallel
子句将被多线程执行。如不指定线程数,一般默认CPU线程数
1 | // parallel: 将被多个线程执行 |
2 |
|
3 | { |
4 | printf("Hello World! ThreadID = %d\n", omp_get_thread_num()); |
5 | } |
6 | /* |
7 | Hello World! ThreadID = 3 |
8 | Hello World! ThreadID = 0 |
9 | Hello World! ThreadID = 1 |
10 | Hello World! ThreadID = 2 |
11 | */ |
omp for
omp for
只在自身处于omp parallel
环境下才有意义。
在omp for
子句中首行需要是可以预测的for循环语句(即某个变量的区间线性变换)。
该for循环会被不同线程刮分为尽可能等长的部分,分配方式由编译器决定。
1 | // for: 多线程并行for循环 |
2 | int sum = 0; |
3 |
|
4 | { |
5 | int mysum = 0; |
6 | |
7 | for (int i = 0; i <= 10000; ++i){ |
8 | mysum += i; |
9 | } |
10 | printf("ThreadID = %d, partial sum = %d\n", omp_get_thread_num(), mysum); |
11 | |
12 | { |
13 | sum += mysum; |
14 | } |
15 | } |
16 | printf("%d\n", sum); |
17 | |
18 | /* |
19 | ThreadID = 3, partial sum = 5469375 |
20 | ThreadID = 1, partial sum = 2344375 |
21 | ThreadID = 2, partial sum = 3906875 |
22 | ThreadID = 4, partial sum = 7031875 |
23 | ThreadID = 5, partial sum = 8594375 |
24 | ThreadID = 0, partial sum = 781875 |
25 | ThreadID = 6, partial sum = 10156875 |
26 | ThreadID = 7, partial sum = 11719375 |
27 | 50005000 |
28 | */ |
注意:
- 子句外面的
sum
默认是shared的,子句里面的mysum
默认是private的 - 如果需要将外部变量改为线程私有可以用
private(...)
和firstprivate(...)
- 通过部分和可以看到,实验环境中的线程分配逻辑为0号拿0-1250,1号拿1251-2500等
- 在对共享变量做写操作时,需要加锁保证线程安全
- 例如此处的
sum += mysum;
- 如果需要出错的实验现象,可以将线程数改成2000。
- 例如此处的
omp parallel for
将parallel和for写在一起,上述代码的等价代码如下:
1 | // parallel for: 等价代码,注意这里的sum无需写private |
2 | sum = 0; |
3 |
|
4 | for (int i = 0; i <= 10000; ++i){ |
5 | sum += i; |
6 | } |
7 | printf("%d\n", sum); |
8 | /* |
9 | 50005000 |
10 | */ |
这里的sum实际上还是被多个线程私有化了,加写private(sum)
会报错。
如果在并行区域内不加锁保护就直接对共享变量迚行写操作,存在数据竞争问题,会导致不可预测的异常结果。
共享数据作为 private、firstprivate、lastprivate、threadprivate、reduction 子句的参数进入并行区域后,就变成线程私有了,不需要加锁保护了。
reductin(运算符: 变量)
会将每个线程最终得到的变量结果以运算符方式归入变量中去。
注意reduction
的用法:
运算标识符 | 各线程私有变量初始值 |
---|---|
+ |
0 |
* |
1 |
- |
0 |
& |
~0 |
omp parallel sections
将每个section手动分配给一个线程(如果线程数不够还是会调度做)进行并行
1 | sum = 0; |
2 | // parallel sections: 多个线程执行若干section |
3 |
|
4 | { |
5 | |
6 | { |
7 | for (int i = 0; i <= 10000; ++i){ |
8 | sum += i; |
9 | } |
10 | printf("ThreadID = %d, sum = %d\n", omp_get_thread_num(), sum); |
11 | } |
12 | |
13 | { |
14 | for (int i = 0; i <= 100; ++i){ |
15 | sum -= i; |
16 | } |
17 | printf("ThreadID = %d, sum = %d\n", omp_get_thread_num(), sum); |
18 | } |
19 | } |
20 | printf("%d\n", sum); |
21 | /* |
22 | ThreadID = 3, sum = -5050 |
23 | ThreadID = 2, sum = 50005000 |
24 | -5050 |
25 | */ |
为了保证并行的效率性:
- 在使用sections时需要尽可能保障每个section的执行时间尽可能相同
- 就像使用parallel for时需要尽可能保障每个线程执行时间尽可能相同
- 否则就可能发生一核有难五核忙等的悲剧
常用参数
num_threads(x)
:指定线程数private(x,y,...)
:指定变量为线程私有,初始值未知;原变量的值不会改变firstprivate(x,y,...)
:指定变量为私有,且需要拷贝主线程的初始值;原变量值不会被改变lastprivate(x,y,...)
:指定变量为私有,初始值未知,在退出并行后,会将serial
理论上的最后一次结果复制给同名原变量shared(x,y,...)
:指定变量为线程共享,注意在写的时候需要加锁critical
:申明一段临界区reduction(op: x)
:指定每个线程创建x
的一个私有拷贝, 在区域的结束处,将用私有拷贝的值通过指定的op
运行符运算,并对原始的参数条目被运算结果的值更新。omp_get_thread_num()
:得到当前线程号
更多内容可以参考此处: