OpenMP理解critical块中的死锁

3
我正在努力理解为什么在并行区域中嵌套关键构造时会发生死锁。
我查阅了以下资源:这个来源,作者写道:
在OpenMP中,如果在关键区域内调用包含另一个关键区域的函数,则可能会发生这种情况。在这种情况下,被调用函数的关键区域将等待第一个关键区域终止-而这永远不会发生。
好的,但为什么呢?此外,来自Hager、Georg和Gerhard Wellein的《Introduction to high performance computing for scientists and engineers》(CRC Press,2010)第149页:
当线程在关键指令内部遇到CRITICAL指令时,它将永远阻塞。
同样的问题,为什么呢?
最后,Chapman、Barbara、Gabriele Jost和Ruud Van Der Pas的《Using OpenMP: portable shared memory parallel programming》(Vol. 10,MIT press,2008)也提供了一个使用锁的示例,但没有使用关键构造。
根据我目前的理解,在嵌套的关键区域中发生死锁有两种不同的可能方式:
第一种情况:
如果两个线程到达嵌套的关键构造(一个关键区域内部另一个关键区域),线程一进入“外部”关键区域,线程二等待。引用Chapman等人的话:
当线程遇到关键构造时,它会等待,直到没有其他线程正在执行具有相同名称的关键区域。
好的,到目前为止都很好。现在,线程一不进入嵌套的关键区域,因为它是一个同步点,在此处线程等待所有其他线程到达后再继续执行。由于第二个线程正在等待第一个线程退出“外部”关键区域,所以它们处于死锁状态。
第二种情况:
两个线程都到达了“外部”关键结构。线程一进入“外部”关键结构,线程二等待。现在,线程一进入“内部”关键结构并停在其隐含的屏障上,因为它正在等待线程二。另一方面,线程二在等待线程一退出“外部”线程,因此两个线程都将永久等待。
以下是产生死锁的小型Fortran代码:
  1   subroutine foo
  2 
  3     !$OMP PARALLEL 
  4     !$OMP CRITICAL 
  5       print*, 'Hallo i am just a single thread and I like it that way'
  6     !$OMP END CRITICAL
  7     !$OMP END PARALLEL 
  8 
  9   end subroutine foo
 10 
 11 program deadlock
 12   implicit none
 13   integer :: i,sum = 0
 14 
 15   !$OMP PARALLEL
 16   !$OMP DO 
 17   do i = 1, 100
 18   !$OMP CRITICAL
 19      sum = sum + i
 20      call foo()
 21   !$OMP END CRITICAL
 22   enddo
 23   !$OMP END DO
 24   !$OMP END PARALLEL
 25 
 26   print*, sum
 27 end program deadlock

所以我的问题是,这两个建议中是否有一个是正确的,或者在这种情况下死锁发生的原因是否还有其他可能性。
1个回答

4

在CRITICAL语句中,没有任何隐含的屏障,也就是说,没有“同步点,在这个点上线程等待其他线程到达”的概念。相反,在进入关键构造之前,线程会等待一个同名的关键构造内的所有线程已经离开该构造。

由于当前OpemMP规则规定不能嵌套具有相同名称的临界构造(请参见OpemMP 4.0第2.16节中的嵌套限制),因此不能嵌套具有相同名称的临界区。这实际上是您问题的答案和讨论的结束-如果违反这一禁令,则可能会发生任何事情。

从实际角度考虑,这种禁止允许实现假设具有相同名称的关键构造不会被嵌套。一种常见的实现选择是,当一个线程遇到关键构造时,它将等待所有线程(包括自己)离开该构造。如果一个线程正在等待,那么它不能离开。这导致死锁。

具有不同名称的临界区可以嵌套。在这种情况下,如果嵌套不一致,则可能会发生死锁。例如:

!$OMP PARALLEL

!$OMP CRITICAL (A)
!$OMP CRITICAL (B)      ! Thread one waiting here.
!...
!$OMP OMP CRITICAL (B)
!$OMP END CRITICAL (A)

!$OMP CRITICAL (B)
!$OMP CRITICAL (A)      ! Thread two waiting here.
!...
!$OMP OMP CRITICAL (A)
!$OMP END CRITICAL (B)

!$END PARALLEL

如果出现这种情况,线程将会等待很长时间。

对您的解释有一个小的后续问题:当线程一进入“外部”临界区时,线程二会在外面等待。现在,线程一正在等待“任何”线程离开“内部”临界区,但这并没有发生,因为一次只能有一个线程进入“外部”临界区。所以它在等待自己离开。这样理解正确吗? - Vincent
1
我更改了一个与您的后续相关的单词——“任何”改为“所有”,以确保正确性。一种合理的实现方式是,当它进入内部关键结构时,线程一(原子地)查询关键部分是否有线程在其中。答案是肯定的,所以线程等待。(如果答案是否定的,线程将(与查询一起原子化)标记关键部分已被占用并进入该部分。)线程最终等待自己。线程二不相关——它可以在外部结构边界处等待,或者飞往月球。 - IanH
谢谢您的评论。 - Vincent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接