同步构造
- 考虑一个简单的示例,在两个不同处理器上的两个线程试图同时增加变量x的值(假定x被初始化为0):
THREAD 1:
increment(x) { x = x + 1; } THREAD 1:
10 LOAD A, (x address) 20 ADD A, 1 30 STORE A, (x address) |
THREAD 2:
increment(x) { x = x + 1; } THREAD 2:
10 LOAD A, (x address) 20 ADD A, 1 30 STORE A, (x address) |
- 一种可能出现的执行顺序:
1. 线程一加载x到寄存器A
2. 线程二加载x到寄存器A
3. 线程一将寄存器A的值+1
4. 线程二将寄存器A的值+1
5. 线程一将寄存器A的值保存到x
6. 线程二将寄存器A的值保存到x
结果将会是1,而不是期望的2.(注:两个cpu,所以"寄存器 A"不是同一个)
- 要避免这种情况,两个线程对x的增加操作必须同步来保证得到正确的结果
- OpenMP提供了几种同步构造来控制一个线程处理与其他线程相关的执行
同步构造
MASTER 指令
目的:
- MASTER指令指定一个只能被主线程执行的区域,其他的线程都将忽略这块代码区域
- 这条指令没有相关隐式关卡
格式:
Fortran |
!$OMP MASTER
block
!$OMP END MASTER |
C/C++ |
#pragma omp master newline
structured_block |
限制:
- 扩充MASTER 块是非法的
CRITICAL 指令
目的:
- CRITICAL指令指定一块同一时间只能被一条线程执行的代码区域
格式:
Fortran |
!$OMP CRITICAL [ name ]
block
!$OMP END CRITICAL |
C/C++ |
#pragma omp critical [ name ] newline
structured_block |
注意:
- 如果一条线程正在一个CRITICAL区域执行而另一个线程到达这个区域,并企图执行,那么它将会被阻塞,直到第一个线程离开这个区域.
- 命名是可选项,使不同的CRITICAL区域共存:
- 命名是全局标志符.具有相同命名的不同的CRITICAL区域被当作同一个区域
- 所有未命名CRITICAL区域被当作同一个区域
限制:
- 扩充CRITICAL 块是非法的.
Example: CRITICAL Construct
- 所有的线程试图并行执行,但由于对x增加的代码被CRITICAL构造包围,在任何时刻均只能有一个线程对此进行读/增加/写操作
Fortran - CRITICAL Directive Example
PROGRAM CRITICAL
INTEGER X X = 0
!$OMP PARALLEL SHARED(X)
!$OMP CRITICAL X = X + 1 !$OMP END CRITICAL
!$OMP END PARALLEL
END |
C / C++ - critical Directive Example
#include <omp.h>
main() {
int x; x = 0;
#pragma omp parallel shared(x) {
#pragma omp critical x = x + 1;
} /* end of parallel section */
} |
BARRIER 指令
目的:
- BARRIER指令同步线程组中的所有线程
- 到达一个BARRIER指令后,线程会原地等待其他的线程,然后恢复并行执行其后的代码
格式:
Fortran |
!$OMP BARRIER |
C/C++ |
#pragma omp barrier newline |
限制:
- 线程组中的所有线程都将执行(不执行)BARRIER区域
- 线程组中的所有线程遇到的work-sharing区域和barrier区域的顺序必须统一
TASKWAIT 指令
目的:
- TASKWAIT构造表示等待从当前线程开始后产生的线程完成工作
格式:
Fortran |
!$OMP TASKWAIT |
C/C++ |
#pragma omp taskwait newline |
限制:
- 因为taskwait构造的语法没有包含一个C语言语句,如何在程序中放置它会有一些限制,它只能放置在允许基本语言语句的地方。诸如if,while,do,switch或者label的语句位置不允许放置。更多细节请查看OpenMP3.0规范。
ATOMIC 指令
目的:
- ATOMIC指令规定在特定的内存位置不允许多线程写入,只能原子更新(atomically).其实,它就是个迷你CRITICAL区域
格式:
Fortran |
!$OMP ATOMIC
statement_expression |
C/C++ |
#pragma omp atomic newline
statement_expression |
限制:
- 只作用于紧随其后的一条语句
- atomic语句必须跟随一个特定的语法.关于这点,请参考最新的OpenMP规定
FLUSH 指令
目的:
- FLUSH指令标识一个同步点,在此,必须提供内存一致可视性的实现.线程可见的变量在此点被写回内存。
- 在OpenMP的圈子里,有关于此指令的一些讨论,你可以有更多的了解
- 引自openmp.org的FAQ:
Q17: !$omp flush指令在一个高速缓存一致的系统上是否必要?
A17:是的,flush指令是必要的,请查看OpenMP规范中的应用示例.指令需要指示编译器相关变量必须对系统内存执行写入/读取操作,也就是说,代码中flush“语句”中的变量不能被保持在一个局部CPU寄存器。高速缓存一致性确保如果一个cpu对内存执行了读/写操作,那么系统中的所有其他CPU在访问此内存地址的时候都会得到相同的值。但是在这点上,OpenMP标准是指示编译器确实插入了读/写机器指令并且没有推迟的一种方法。在为循环产生高效机器代码时,将一个循环中的变量保持在寄存器中是相当普遍的。
更多细节请查看最新的 OpenMP 文献 .
格式:
Fortran |
!$OMP FLUSH (list) |
C/C++ |
#pragma omp flush (list) newline |
注意:
- 为了避免所有的变量都被清洗(flush),(list)包含了一系列要清洗的变量。对于(list)中的指针,是指针本身而不是指针指向的对象被清洗。
- 实现必须确保先前的对线程可见变量的修改在此点过后对所有线程可见。编译器必须将寄存器的值写入内存,硬件可能也需要清洗缓冲区,等等.
- 下表的指令隐式包含FLUSH指令.如果NOWAIT指令存在,那么该指令将不被隐式包含.
Fortran |
C / C++ |
BARRIER |
barrier |
ORDERED 指令
目的:
- ORDERED指令表示其封装的循环的迭代将按相同顺序执行,就好象他们在一个串行处理器上执行一样。
- 在执行迭代分支之前,线程需要等待前面的迭代完成
- 在一个DO/for循环中使用ORDERED子句
- ORDERED指令提供了当在一个循环中需要按顺序执行时的微调方式。否则,它就不必要。
格式:
Fortran |
!$OMP DO ORDERED [clauses...] (loop region)
!$OMP ORDERED
(block)
!$OMP END ORDERED
(end of loop region) !$OMP END DO |
C/C++ |
#pragma omp for ordered [clauses...] (loop region)
#pragma omp ordered newline
structured_block
(endo of loop region) |
限制:
- ORDERED指令只能在以下指令的动态范围出现:
- DO 或者 PARALLEL DO (Fortran)
- for 或者parallel for (C/C++)
- 在ordered区域,任何时刻只能有一个线程
- 扩充ORDERED块是非法的
- 一个循环的迭代不能执行同一个ORDERED指令超过一次,并且只能执行一个ORDERED指令
- 包含ORDERED指令的循环必须跟随一个ORDERED子句
THREADPRIVATE 指令
目的:
- THREADPRIVATE指令用来将全局变量(c/c++)或者公共快(Fortran)在执行并行区域时相对线程本地和持久化(即变量线程私有化)
Format:
Fortran |
!$OMP THREADPRIVATE (/cb/, ...) cb is the name of a common block |
C/C++ |
#pragma omp threadprivate (list) |
注意:
- 指令必须在声明变量列表/公共块后使用.每个线程都会得到变量/公共块的拷贝,这样一个线程写的数据对另外的线程不可见,例如:
Fortran - THREADPRIVATE Directive Example
PROGRAM THREADPRIV
INTEGER A, B, I, TID, OMP_GET_THREAD_NUM REAL*4 X COMMON /C1/ A
!$OMP THREADPRIVATE(/C1/, X)
C Explicitly turn off dynamic threads CALL OMP_SET_DYNAMIC(.FALSE.)
PRINT *, '1st Parallel Region:' !$OMP PARALLEL PRIVATE(B, TID) TID = OMP_GET_THREAD_NUM() A = TID B = TID X = 1.1 * TID + 1.0 PRINT *, 'Thread',TID,': A,B,X=',A,B,X !$OMP END PARALLEL
PRINT *, '************************************' PRINT *, 'Master thread doing serial work here' PRINT *, '************************************'
PRINT *, '2nd Parallel Region: ' !$OMP PARALLEL PRIVATE(TID) TID = OMP_GET_THREAD_NUM() PRINT *, 'Thread',TID,': A,B,X=',A,B,X !$OMP END PARALLEL
END
Output:
1st Parallel Region: Thread 0 : A,B,X= 0 0 1.000000000 Thread 1 : A,B,X= 1 1 2.099999905 Thread 3 : A,B,X= 3 3 4.300000191 Thread 2 : A,B,X= 2 2 3.200000048 ************************************ Master thread doing serial work here ************************************ 2nd Parallel Region: Thread 0 : A,B,X= 0 0 1.000000000 Thread 2 : A,B,X= 2 0 3.200000048 Thread 3 : A,B,X= 3 0 4.300000191 Thread 1 : A,B,X= 1 0 2.099999905 |
C/C++ - threadprivate Directive Example
#include <omp.h>
int a, b, i, tid; float x;
#pragma omp threadprivate(a, x)
main () {
/* Explicitly turn off dynamic threads */ omp_set_dynamic(0);
printf("1st Parallel Region:/n"); #pragma omp parallel private(b,tid) { tid = omp_get_thread_num(); a = tid; b = tid; x = 1.1 * tid +1.0; printf("Thread %d: a,b,x= %d %d %f/n",tid,a,b,x); } /* end of parallel section */
printf("************************************/n"); printf("Master thread doing serial work here/n"); printf("************************************/n");
printf("2nd Parallel Region:/n"); #pragma omp parallel private(tid) { tid = omp_get_thread_num(); printf("Thread %d: a,b,x= %d %d %f/n",tid,a,b,x); } /* end of parallel section */
}
Output:
1st Parallel Region: Thread 0: a,b,x= 0 0 1.000000 Thread 2: a,b,x= 2 2 3.200000 Thread 3: a,b,x= 3 3 4.300000 Thread 1: a,b,x= 1 1 2.100000 ************************************ Master thread doing serial work here ************************************ 2nd Parallel Region: Thread 0: a,b,x= 0 0 1.000000 Thread 3: a,b,x= 3 0 4.300000 Thread 1: a,b,x= 1 0 2.100000 Thread 2: a,b,x= 2 0 3.200000 |
- 第一次进入并行区域时,THREADPRIVATE中的变量或者公共块都被假定为未定义,直到在PARALLEL指令后指定COPYIN子句。
- THREADPRIVATE 变量跟 PRIVATE (稍后讨论)不一样,因为他们能保持在代码的不同并行区域 。
限制:
- 只有线程的动态机制处于“关闭”状态并且各个并行块的线程数量保持不变,THREADPRIVATE对象中的数据才能确保持久化。动态线程没有默认设置。
- THREADPRIVATE指令必须在每一个线程私有变量/公共块的声明之后才能出现。
-
Fortran:THREADPRIVATE只能作用已命名的公共块 。