OpenMP编程(5)—同步结构(master、critical、barrier、atomic、flush、ordered)

    技术2022-07-12  71

    OpenMP的同步结构(Synchronization Constructs)指令包master、critical、barrier、atomic、flush、ordered等

    1. master指令
    master指令指定的区域只由主线程执行,团队中其他线程都跳过该区域代码本指令没有隐含的barrier,即其他线程不用再master区域结束处同步,可立即执行后续代码。 代码示例如下 #pragma omp parallel { #pragma omp master { printf("in master thread %d\n", omp_get_thread_num()); } printf("out master thread %d\n", omp_get_thread_num()); }

    运行结果如下,可见只有主线程0执行了master区域代码。

    2. critical指令
    critical指令指定的代码区域,一次只能由一个线程执行。如果一个线程当前正在一个critical区域内执行,而另一个线程到达该区域并试图执行它,它将阻塞,直到第一个线程退出该区域。

    其语法格式如下

    #pragma omp critical [ name ] newline structured_block

    其中有一个可选名称选项,该选项允许存在多个不同的critical区域,其特性如下:

    该名称充当critical区域全局标识符,相同名称的不同临界区域被视为同一区域。所有未命名的critical区域均视为同一区域。 critical用法很简单,下面演示下命名critical的特性
    2. 1 不同名称的多个critical区域

    代码示例如下:

    #pragma omp parallel sections { #pragma omp section { #pragma omp critical (critical1) { for (int i=0; i < 5; i++) { printf("section1 thread %d excute i = %d\n", omp_get_thread_num(), i); Sleep(200); } } } #pragma omp section { #pragma omp critical (critical2) { for (int j=0; j < 5; j++) { printf("section2 thread %d excute j = %d\n", omp_get_thread_num(), j); Sleep(200); } } } }

    运行结果如下,由于两个section中采用了不同名字的critical,所以两个线程能分别同时运行各自的临界区代码。

    2. 2 相同名称的多个critical区域

    代码示例如下:

    #pragma omp parallel sections { #pragma omp section { #pragma omp critical (critical1) { for (int i=0; i < 5; i++) { printf("section1 thread %d excute i = %d\n", omp_get_thread_num(), i); Sleep(200); } } } #pragma omp section { #pragma omp critical (critical1) { for (int j=0; j < 5; j++) { printf("section2 thread %d excute j = %d\n", omp_get_thread_num(), j); Sleep(200); } } } }

    运行结果如下,两个section本来是两个线程并行处理的,但由于采用了相同名字的critical将两个区域的代码包围,根据前面所说的相同名字的不同临界区属于同一区域,只有一个线程能进入,因此两个section线程是串行的。 尤其要注意如果多个临界区未命名,则也属于同一区域,那么只有一个线程能进入其中一个临界区。

    3. barrier指令
    barrier指令同步团队中的所有线程。组内任何线程到达barrier指令时将在该点等待,直到所有其他线程都到达该barrier处为止。然后所有线程才继续并行执行后续代码。 代码示例如下 #pragma omp parallel { printf("thread %d excute first print\n", omp_get_thread_num()); #pragma omp barrier printf("thread %d excute second print\n", omp_get_thread_num()); }

    当无barrier和有barrier时,运行结果分别如下。可见,有barrier时,各线程在完成第一条打印后,会在barrier后同步后,在执行第二条打印。

    4. atomic指令
    atomic指令指定必须以原子方式更新某变量的内存,而不是让多个线程尝试对其进行写入。该指令提供了一个最小的关键区,其效率比关键区高。 看如下的示例: int x=0; #pragma omp parallel num_threads(6) { for(int i=0; i<100000; ++i) x++; } printf("%d", x);

    本来有6个线程,每个执行100000次循环,则x的预期值是600000,但实际结果小于该值,原因如下: x++的汇编指令如下三条: LOAD A, (x address) ADD A, 1 STORE A, (x address) 当两个线程都在执行该指令时,可能会出现如下情况: 线程1将x内存值加载到寄存器A中。 线程2将x内存值加载到寄存器A中。 线程1对寄存器A增加1 线程2对寄存器A增加1 线程1将寄存器A存储到x内存 线程2将寄存器A存储到x内存 则运行完毕后,x不是增加2,而是增加1。 如何高效避免该问题,可以按如下采用atomic指令(当然采用critical也可以,只不过atomic效率更高)

    int x=0; #pragma omp parallel num_threads(6) { for(int i=0; i<100000; ++i) #pragma omp atomic x++; } printf("%d", x);

    该程序运行后,x值为600000。

    5. flush指令
    flush指令标识一个同步点,在该点上list中的变量都要被写回内存,而不是暂存在寄存器中,这样保证多线程数据的一致性。由于线程将共享变量更新后,其值可能暂存在寄存器中,并没有写到变量所在内存中,这样会导致其他线程不知道该更新而使用共享变量的旧值进行运算,可能会得到错误的结果。通过使用flush指令,要求相应的变量值刷新到内存中,从而保证线程读取到的共享变量的最新值。以下指令隐含flush操作:barrier、parallel 、critical 、ordered、for 、sections、single。
    6. ordered指令
    ordered指令指定区域的循环迭代将按串行顺序执行,与单个处理器处理结果顺序一致ordered指令只能用在for或parallel for中 代码示例如下 #pragma omp parallel { #pragma omp for ordered for (int i = 0; i < 10; ++i) { #pragma omp ordered { printf("thread %d excute i = %d\n", omp_get_thread_num(), i); } } }

    运行结果如下,可以看出因为增加了ordered指令,for循环是按照i从小到大顺序执行的。

    Processed: 0.014, SQL: 12