同步构造:Synchronization Constructs
(1)什么是同步构造?
OpenMP用于并行编程,自然,就会有“数据竞争”等相关问题。所以,OpenMP也提供了一些同步构造的指令,用于进行同步(synchronization)。
(2)同步构造之master指令
指令功能:master指令指定一个区域只会被一个team中的主线程(master thread)执行,team内的其他线程都会忽略此区域。此指令没有隐含的等待。
指令格式:
#pragma omp master newline structured_block指令限制:master指令不允许跳出或跳进(goto等)master块。
(3)同步构造之critical指令
指令功能:critical指令指定某一区域的代码每次只能同时被一个线程执行。
指令格式:
#pragma omp critical [ (name) ] newline structured_block
指令限制:critical指令不允许跳出或跳进(goto等)critical块。
说明:
如果一个执行正在执行critical块的代码,并且其他线程也达到了critical块,那么这个线程会阻塞等待,直到第一个线程执行完critical的代码。其实,critical就相当于简化的win32多线程中的临界区或锁的概念。
实例:
#include <stdio.h> int g; #define ADD_COUNT 100000 int main(int argc, char *argv[]) { printf("Masterthread started\n\n"); #pragma omp parallel { for (int i = 0; i < ADD_COUNT;i ++) { #pragma omp critical g = g + 1; // Will cause data races without critical directive. } }// End of parallel region printf("g = %d\n", g); printf("Expected g = %d\n",ADD_COUNT * 4); // Assume the thread number is 4. printf("Masterthread finished\n"); return(0); }
这里,如果不使用critical,得到的结果将会不正确,就是由于数据竞争引起的。
下面的例子是同样的数据竞争的例子,这两个例子比较一下也可以加深对parallel和parallel for的区别。
#include <stdio.h> int g; #define ADD_COUNT 1000000 int main(int argc, char *argv[]) { printf("Masterthread started\n\n"); #pragma omp parallel for for (int i = 0; i < ADD_COUNT;i ++) { #pragma omp critical g = g + 1; // Will cause data races without critical directive. } // End of parallel for region printf("g = %d\n",g); printf("Expected g = %d\n", ADD_COUNT); printf("Masterthread finished\n"); return(0); }
关于name选项:在critical中,还有一个name的参数,critical可以指定代码段的name,同一个name的代码段被当成同一个代码段,所有未命名的代码段都当作一个代码段。
下面的两个例子对比来理解name的作用:
#include <stdio.h> int g; int g1; #define ADD_COUNT 1000000 int main(int argc, char *argv[]) { printf("Masterthread started\n\n"); #pragma omp parallel for for (int i = 0; i < ADD_COUNT;i ++) { #pragma omp critical (namex) g = g + 1; // Will cause data races without critical directive. #pragma omp critical (namey) g1 = g1 + 1; // Will cause data races without critical directive. } // End of parallel for region printf("g = %d, g1 = %d\n",g, g1); printf("Expected g = g1 = %d\n", ADD_COUNT); printf("Masterthread finished\n"); return(0); }
这个例子定义了两个全局变量g和g1,分别运算,所以可以使用两个critical进行同步,那么第一个线程在操作namex段的代码(g)的时候,另一个线程(第二个线程)可能在等待,然后,第一个线程进入namey代码段,那么此时第二个线程就可以执行namex段的代码了,由于分别是g和g1的操作,这样是完全允许同时进行的。然而,下面的例子,使用不同的命名的critical,就会导致结果错误:
#include <stdio.h> int g; int g1; #define ADD_COUNT 1000000 int main(int argc, char *argv[]) { printf("Masterthread started\n\n"); #pragma omp parallel for for (int i = 0; i < ADD_COUNT;i ++) { #pragma omp critical (namex) g = g + 1; #pragma omp critical (namey) g = g + 1; // Still data races! } // End of parallel for region g1 = g; printf("g = %d, g1 = %d\n",g, g1); printf("Expected g = g1 = %d\n",ADD_COUNT); printf("Masterthread finished\n"); return(0); }
(4)同步构造之barrier指令
指定作用:barrier是最简单的同步指令了,用于同步team中的所有线程。当一个线程达到了barrier后,它会在此处等待知道其它所有线程都执行到此处。
指令格式:
#pragma omp barrier newline
(5)同步构造之taskwait指令
OpenMP3.0新增加的指令,和task构造指令相关。
(6)同步构造之atomic指令
指令作用:指定某一内存位置必须原子操作的形式更新。相当于简化的critical的使用。
指令格式:
#pragma omp atomic newline statement_expression
说明:atomic后面不是一个structed bock,而是一个表达式。atomic的使用有一些格式要求,具体参考OpenMP spec说明。
(7)同步构造之flush指令
指令作用:指定一个同步点,在此处,线程变量会写入到真实的内存中去。
指令格式:
#pragma omp flush (list) newline
(8)同步构造之ordered指令
指定for循环迭代和串行一样的顺序执行。
只能用于指令了ordered子句的for指令。
指令格式:
#pragma omp for ordered [clauses...] (loop region) #pragma omp ordered newline structured_block (endo of loop region)
说明:
对于基本的使用,master,critical,barrier指令需要掌握和理解,其他指令,在以后逐步理解。
OpenMP Tutorial(Synchronization Constructs):https://computing.llnl.gov/tutorials/openMP/#Synchronization