在使用 OMP 并行化较大的代码时,我遇到了一个非常奇怪的行为。我花了几个小时才把它归结为这个非常简单的 MWE。当在 OMP DO 循环中调用子例程
testsub
时,在多核上运行并使用 x
初始化 testsub
变量时,logical :: done=.false.
的结果会出错。并使用 -g
进行编译。我基本上不需要也不依赖初始化,因为无论如何逻辑都会在子例程体内设置为false
。但我万万没想到,并行运行时,逻辑done
可以在到达do循环之前变为真。
program test
use omp_lib
implicit none
integer :: ii
real(8) :: x
call omp_set_num_threads(4)
!$OMP PARALLEL DEFAULT(NONE), &
!---------------------------- private ---------------------------------
!$OMP& FIRSTPRIVATE(x,ii)
!$OMP DO
do ii=1,8
x = 444.d0
call testsub(x)
write(*,*) 'Should be 555.d0: ', x
enddo ! ii
!$OMP END DO
!$OMP END PARALLEL
contains
subroutine testsub(x)
implicit none
real(8),intent(inout) :: x
logical :: done=.false.
! logical :: done
done = .false.
write(*,*) 'Initial value: ', x
do while (.not. done)
x = 555.d0
done = .true.
enddo
endsubroutine testsub
endprogram test
使用
ifort -g test.f90 -o test -fopenmp
编译此代码,结果(对于 omp_set_num_threads>1)如下所示:
Initial value: 444.000000000000
Initial value: 444.000000000000
Initial value: 444.000000000000
Initial value: 444.000000000000
Should be 555.d0: 555.000000000000
Initial value: 444.000000000000
Should be 555.d0: 555.000000000000
Should be 555.d0: 444.000000000000
Should be 555.d0: 444.000000000000
Should be 555.d0: 444.000000000000
Initial value: 444.000000000000
Should be 555.d0: 555.000000000000
Initial value: 444.000000000000
Initial value: 444.000000000000
Should be 555.d0: 444.000000000000
Should be 555.d0: 444.000000000000
对我来说,逻辑
done
似乎被并行进程覆盖,但我不明白为什么。
对于这个 MWE,我找到了两种摆脱错误行为的方法:
-g
标志进行编译logical :: done
而不是 logical :: done=.false.
对于我的实际代码,只有后一种方法有效。虽然没有必要,但我发现在声明时初始化这些变量可以很好地提醒我自己默认状态应该是什么,并且我不明白为什么在使用 OMP 时这会是错误的。
有任何关于发生什么事的线索吗?
你已经陷入了隐含保存陷阱。
logical done = .false.
实际上等同于 logical, save :: done = .false
:隐含 save
属性。
具有此属性的变量是静态的,这意味着它在整个执行过程中具有保留的内存位置,结果是变量的内容从例程的一次调用到另一次调用都被保留。但这也意味着如果在并行区域内调用例程,则该变量在所有线程之间共享:只要线程只读取该变量就可以,但是如果它们修改它,则会导致竞争条件。
简而言之,
save
属性是多线程杀手。
回想起来,“隐含的
save
”行为显然是 Fortran 90 中引入的设计缺陷,但现在我们必须忍受它(尽管已经有建议来缓解它)。
此外,如果您不使用此类初始化(在本例中您绝对不应该使用),请不要依赖任何默认初始化:某些编译器确实会初始化变量,但这不是 Fortran 标准所要求的。因此,依赖它的代码是非标准且不可移植的。