在并行 Fortran do 循环中调用内部子例程时,子例程无法访问迭代变量的正确值

When calling an internal subroutine in a parallel Fortran do loop, correct value of iteration variable is not accessible to the subroutine

我试图编写一个 Fortran 程序,其中在并行 do 循环中调用内部子例程。因为子例程除了在这个循环中没有在任何地方被调用,并且因为迭代变量 i 是全局的,所以我没有看到需要将它传递给子例程。这是突出显示问题的程序的简化大纲:

program test
  integer :: i
  i=37

  $omp parallel do private(i)
  do i=1,5
    call do_work
  enddo
  $omp end parallel do

contains
  subroutine do_work
    print *,i
  end subroutine do_work
end program test

我正在编译这个程序使用:

gfortran -O0 -fopenmp -o test test.f90

我在一台8核机器上用gfortran 4.4.6编译,在另一台8核机器上用gfortran 5.4.0编译,得到:

37
37
37
37
37

当然,在没有 -fopenmp 标志的情况下编译时,我得到了预期的输出:

1
2
3
4
5

所以 i 的预循环值似乎是 do_work 在每个线程中看到的值。为什么子例程看不到 i 的线程本地值?为什么将 i 作为参数传递给子例程可以解决问题?我是 OpenMP 的新手,如果答案很明显,我深表歉意。

使用 OpenMP,当您的程序进入 do 循环时,将创建一个“thread”。这类似于主程序调用子程序,只是主程序的变量可用于子程序。

然而,由循环分隔的并行区域将创建私有变量的副本,因此每个线程都有自己的 i 版本。您的子例程只能看到 "supervisor" 程序的 i,而不是线程的本地副本。当使用显式参数时,子例程将被显式告知为 i.

使用 "thread-local" 值

一般来说(对于 OpenMP),仔细考虑哪些变量是并行区域的本地变量以及哪些变量可以保留是很重要的"global"。

OpenMP 标准未指定程序的行为。

如果您不将 i 作为参数传递,并且您希望 i 对构造中的每个线程都是私有的(物理上出现在并行和结束并行之间的源指令)和区域内(在这些指令之间执行的源代码,那么您需要提供 i OpenMP threadprivate 属性。

在过程 do_work 中,变量 i 由主机关联引用,并且在过程中,它不会出现在 OpenMP 构造中的词法中 - 因此在过程中它是一个在区域中引用但不在构造中引用的变量。

通常 OpenMP 4.5 的 2.15.1.2 指定将共享过程中对 i 的引用。

但是因为 i 是隐式的(因为它是一个 do 循环索引)并且在构造中显式私有,2.15.3.3 声明未指定是否在该区域中引用 i 但是不在构造中的是原始(共享)项目或私有副本。

当您将 i 作为参数 "by reference" 传递时,虚拟参数与实际参数具有相同的数据共享属性 - 即,如果您将 i 传递给过程,它将变为私人。