【问题标题】:OpenMP parallel for with ordered and critical directivesOpenMP 与有序指令和关键指令并行
【发布时间】:2020-06-27 06:59:50
【问题描述】:

我一直在尝试了解 OpenMP 并行 for 循环在与临界区和有序指令结合时的工作原理。有几个代码示例让我感到困惑:

1. OpenMP 并行 for 循环用于使用循环索引 i 和线程 ID 初始化数组 s。没有使用ordered 指令或临界区。


    #include <stdio.h>
    #include <omp.h>
    
    #define N       10
    #define CHUNKSIZE   1
    
    int main(int argc, char* argv[])
    {    
        int i, chunk = CHUNKSIZE;    
        char s[N][22];
    
    #pragma omp parallel for shared(s,chunk) private(i)  schedule(static, chunk) 
        for (i = 0; i < N; ++i)
        {
            int tid = omp_get_thread_num();
            sprintf(s[i], "%d:%d", i, tid);
            printf("i: %d tid: %d\n", i, tid);
        }
    
        puts("\nArray initialization order:");
        for (i = 0; i < N; ++i)
            puts(s[i]);
    
    }

它打印以下内容:

i: 7 tid: 7
i: 4 tid: 4
i: 5 tid: 5
i: 6 tid: 6
i: 0 tid: 0
i: 8 tid: 0
i: 3 tid: 3
i: 1 tid: 1
i: 2 tid: 2
i: 9 tid: 1

Array initialization order:
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:0
9:1

我无法弄清楚为什么s 包含i 索引(第一个数字),尽管没有ordered 指令,为什么printf("i: %d tid: %d\n", i, tid) 以不同的顺序显示它们?

2.ordered 添加到omp parallel for 子句似乎不会改变任何东西,除非将omp ordered 放在循环体中。

#pragma omp parallel for shared(s,chunk) private(i)  schedule(static, chunk) ordered
    for (i = 0; i < N; ++i)
    {
        int tid = omp_get_thread_num();
        sprintf(s[i], "%d:%d", i, tid);
        printf("i: %d tid: %d\n", i, tid);
    }

产生与之前相同的结果:sprintf(s[i], "%d:%d", i, tid) 使用严格的i 序列初始化数组,而printf("i: %d tid: %d\n", i, tid) 以任意顺序打印i

#pragma omp parallel for shared(s,chunk) private(i)  schedule(static, chunk) ordered
    for (i = 0; i < N; ++i)
    {
        int tid = omp_get_thread_num();
        sprintf(s[i], "%d:%d", i, tid);
#pragma omp ordered
        printf("i: %d tid: %d\n", i, tid);
    }

现在一切都按照i的顺序发生:

i: 0 tid: 0
i: 1 tid: 1
i: 2 tid: 2
i: 3 tid: 3
i: 4 tid: 4
i: 5 tid: 5
i: 6 tid: 6
i: 7 tid: 7
i: 8 tid: 0
i: 9 tid: 1

Array initialization order:
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:0
9:1

再说一次,我不明白为什么我们需要将 omp ordered 放在循环体中以强制执行打印顺序,而数组初始化不需要这样做。

3. 使用临界区确保一次只有一个线程执行循环体:

#pragma omp parallel for shared(s,chunk) private(i)  schedule(static, chunk) ordered
    for (i = 0; i < N; ++i)
#pragma omp critical
    {
        int tid = omp_get_thread_num();
        sprintf(s[i], "%d:%d", i, tid);
        printf("i: %d tid: %d\n", i, tid);
    }

同样,以任意顺序打印i,并以i的严格顺序初始化s

i: 1 tid: 1
i: 4 tid: 4
i: 3 tid: 3
i: 2 tid: 2
i: 5 tid: 5
i: 0 tid: 0
i: 7 tid: 7
i: 6 tid: 6
i: 8 tid: 0
i: 9 tid: 1

Array initialization order:
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:0
9:1

这完全令人困惑,因为据我了解,临界区必须保证 sprintfprintf 语句由同一个线程执行而没有任何中断。

我们将不胜感激任何帮助解决这个问题。

【问题讨论】:

    标签: c++ multithreading loops for-loop openmp


    【解决方案1】:

    我无法弄清楚为什么s 包含i 索引(第一个数字)尽管没有有序指令,但为什么printf("i: %d tid: %d\n", i, tid) 以不同的顺序显示它们?

    使用静态调度,循环迭代和执行它的线程之间存在固定映射,这就是为什么无论你运行程序多少次,如果线程数保持不变,s[i] 将始终设置为到"i:same_thread_id"。打印s[] 发生在并行区域外的顺序循环中,因此输出是有序的。如果那个循环打印出乱序的东西,我会更惊讶。至于并行区域内的printf() 调用,您有schedule(static,1),这意味着每次迭代都由不同的线程执行,并且以任意顺序运行。

    ordered 添加到omp parallel for 子句似乎不会改变任何东西,除非将omp ordered 放在循环体中。

    这正是ordered 的工作原理。有ordered 子句和ordered 区域。该子句修改了for 工作共享结构的行为,并启用了内部指定区域的有序执行。有序执行需要额外的同步要求才能正常工作,否则不需要,这就是该子句存在的原因。此外,该区域存在,因此只有一部分循环可以按顺序运行。让整个循环体按顺序运行是没有意义的,因为它与顺序(非并行)循环执行没有什么不同。详情请见我的this answer

    这完全令人困惑,因为据我了解,临界区必须保证 sprintfprintf 语句由同一线程执行而不会中断。

    关键部分保证没有两个线程同时执行相同的代码区域。它们绝不会强制执行遇到线程的顺序。由于没有两个线程访问s[] 的相同元素,因此在 3. 中有一个临界区不会改变任何事情。它将循环执行序列化,因为没有两个线程可以同时执行主体,但它不会使循环按顺序运行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-28
      • 1970-01-01
      • 2020-05-24
      • 1970-01-01
      相关资源
      最近更新 更多