【问题标题】:Can I put multiple ordered statements in one ordered for loop (OpenMP)?我可以将多个有序语句放在一个有序的 for 循环 (OpenMP) 中吗?
【发布时间】:2015-06-25 07:19:38
【问题描述】:

我刚刚发现虽然这个 C 代码给出了一个有序的整数列表(如预期的那样):

#include <stdio.h>
#include <unistd.h>
#include <omp.h>

int main() {
#pragma omp parallel for ordered schedule(dynamic)
  for (int i=0; i<10; i++) {
#pragma omp ordered
    {
    printf("%i             (tid=%i)\n",i,omp_get_thread_num(); fflush(stdout);
    }
  }
}

同时使用 gcc 和 icc,以下会产生不良行为:

#include <stdio.h>
#include <unistd.h>
#include <omp.h>

int main() {
#pragma omp parallel for ordered schedule(dynamic)
  for (int i=0; i<10; i++) {
#pragma omp ordered
    {
    printf("%i             (tid=%i)\n",i,omp_get_thread_num()); fflush(stdout);
    }

    usleep(100*omp_get_thread_num());
    printf("WORK IS DONE  (tid=%i)\n",omp_get_thread_num()); fflush(stdout);
    usleep(100*omp_get_thread_num());

#pragma omp ordered
    {
    printf("  %i           (tid=%i)\n",i,omp_get_thread_num()); fflush(stdout);
    }
  }
} 

我希望看到的是:
0
1
2
3
4
5
6
7
8
9
工作完成
工作完成
工作完成
工作完成
工作完成
工作完成
工作完成
工作完成
工作完成
工作完成
0
1
2
3
4
5
6
7
8
9

但是用 gcc 是 get:
0 (tid=5)
工作完成 (tid=5)
0 (tid=5)
1 (tid=2)
工作已完成 (tid=2)
1 (tid=2)
2 (tid=0)
工作已完成 (tid=0)
2 (tid=0)
3 (tid=6)
工作已完成 (tid=6)
3 (tid=6)
4 (tid=7)
工作完成 (tid=7)
4 (tid=7)
5 (tid=3)
工作完成 (tid=3)
5 (tid=3)
6 (tid=4)
工作完成 (tid=4)
6 (tid=4)
7 (tid=1)
工作已完成 (tid=1)
7 (tid=1)
8 (tid=5)
工作完成 (tid=5)
8 (tid=5)
9 (tid=2)
工作已完成 (tid=2)
9 (tid=2)
(所以一切都井井有条——即使是可并行化的工作部分)

还有 icc:
1 (tid=0)
2 (tid=5)
3 (tid=1)
4 (tid=2)
工作已完成 (tid=1)
工作完成 (tid=3)
3 (tid=1)
6 (tid=4)
7 (tid=7)
8 (tid=1)
工作已完成 (tid=0)
5 (tid=6)
工作已完成 (tid=2)
1 (tid=0)
9 (tid=0)
工作已完成 (tid=0)
工作完成 (tid=5)
工作已完成 (tid=1)
9 (tid=0)
0 (tid=3)
8 (tid=1)
工作完成 (tid=4)
工作已完成 (tid=6)
2 (tid=5)
工作完成 (tid=7)
6 (tid=4)
5 (tid=6)
4 (tid=2)
7 (tid=7)
(所以即使是有序的子句也没有得到任何命令)

在一个有序循环中使用多个有序子句未定义行为还是这里发生了什么?在我能找到的任何 OpenMP 文档中,我都找不到任何不允许每个循环使用多个子句的内容。

我知道在这个简单的例子中我可以像

int main() {  
  for (int i=0; i<10; i++) {  
    printf("%i             (tid=%i)\n",i,omp_get_thread_num()); fflush(stdout);  
  }  
#pragma omp parallel for schedule(dynamic)  
  for (int i=0; i<10; i++) {  
    usleep(100*omp_get_thread_num());  
    printf("WORK IS DONE  (tid=%i)\n",omp_get_thread_num()); fflush(stdout);  
    usleep(100*omp_get_thread_num());  
  }  
  for (int i=0; i<10; i++) {  
    printf("  %i           (tid=%i)\n",i,omp_get_thread_num()); fflush(stdout);  
  }          
}  

所以我不是在寻找解决方法。我真的很想了解这里发生了什么,这样我就可以处理真实情况而不会遇到任何破坏性/意外情况。

我真的希望你能帮助我。

【问题讨论】:

    标签: c gcc openmp undefined-behavior icc


    【解决方案1】:

    根据OpenMP 4.0 API specifications你不能。

    循环指令中只能出现一个有序子句(第 58 页)

    【讨论】:

    • 非常感谢!正是我想要的。
    【解决方案2】:

    我对并行编程有点陌生,但我会尽力帮助你。

    我已经修改了你的代码并测试了这个:

    #include <stdio.h>
    #include <unistd.h>
    #include <omp.h>
    
    int main() {
    
      #pragma omp parallel num_threads(8)
      {
    
        #pragma omp for ordered schedule(dynamic)
        for (int i=0; i<10; i++) {
    
              #pragma omp ordered
              printf("%i (tid=%i) \n",i,omp_get_thread_num()); fflush(stdout);
    
        }
    
        printf("WORK IS DONE  (tid=%i)\n",omp_get_thread_num()); fflush(stdout);
    
      }
    
    
    }
    

    根据您用于编译示例的机器调整线程数。代码中的问题是对表示工作已完成的 printf 的访问是随机完成的,每个线程都将独立执行这部分。在我的示例中,我让 for 循环的迭代按照有序子句状态执行,然后 for 子句隐式屏障让每个线程等待,直到它们都在 for 循环和 for 之后立即到达代码位置子句,然后每个打印出“工作完成”。如果您不使用 for 子句并且想要获得相同的输出,则可以使用显式屏障,或者换句话说,#pragma omp 屏障。

    注意:“pragma omp parallel”也使用隐式屏障,在此之后已创建的每个线程都将被销毁

    这是我得到的一个可能的输出:

    0 (tid=7) 
    1 (tid=5) 
    2 (tid=0) 
    3 (tid=4) 
    4 (tid=1) 
    5 (tid=3) 
    6 (tid=2) 
    7 (tid=7) 
    8 (tid=5) 
    9 (tid=0) 
    
    WORK IS DONE  (tid=5)
    WORK IS DONE  (tid=2)
    WORK IS DONE  (tid=1)
    WORK IS DONE  (tid=4)
    WORK IS DONE  (tid=0)
    WORK IS DONE  (tid=7)
    WORK IS DONE  (tid=3)
    WORK IS DONE  (tid=6)
    

    如果这是您希望看到的那种输出,这是实现它的一种可能方式。希望这会有所帮助,如有必要,请毫不犹豫地寻求进一步的帮助。继续编码!

    【讨论】:

    • 感谢您花时间和精力尝试回答我的问题。看来我对我的问题并不完全清楚。在这种情况下,我不是在寻找如何获得正确输出的解决方案。我正在寻找的是解释,为什么我在第二个 sn-p 中编写的代码没有产生预期的行为(按顺序计数 1 到 10,然后“工作完成”无序,然后再次计数 1 到 10命令)。你写“你的代码中的问题是对 printf 的访问表明工作已经完成是随机完成的”——这绝对是有意的。最好的问候,亨利
    • 不客气,我想知道您的问题是否得到最低限度的回答。问题是,如果您期望该输出,则需要使用某种屏障在特定时刻阻塞所有线程。在这种情况下,使用“ordered”子句使执行仅在“ordered”子句内尊重循环迭代的自然顺序,但这并不意味着循环内的每一段代码都将以这种方式执行。希望这有助于澄清您的问题:)
    • 这就是为什么我将两个计数 printfs 分别放在它们自己的有序子句中,而工作部分(不应订购)没有。但是 g++ 似乎也在工作部分周围放置了另一个有序子句,而 icpc 似乎忽略了所有有序子句,除非它恰好是每个有序循环一个。因此,我仍然怀疑每个有序循环的多个有序子句是未定义的行为。我只是在 OpenMP 标准中找不到该规范。我只是希望有人可以指出我或解释我所缺少的。再次感谢。
    • 好的,希望你能找到答案。我在 openmp 方面的专业知识可能不够先进,无法找到令人满意的答案,但我也会继续寻找。最好的问候:)
    猜你喜欢
    • 1970-01-01
    • 2022-07-22
    • 2018-03-11
    • 1970-01-01
    • 1970-01-01
    • 2014-10-30
    • 1970-01-01
    • 2020-03-26
    • 2020-03-23
    相关资源
    最近更新 更多