您的程序具有未指定的行为。请参阅OpenMP 5.0 规范1中的第 2.8 节:
团队中的所有线程都必须遇到每个工作共享区域,或者根本不遇到每个工作共享区域
这意味着任何类型的分支(if
等while
)对于围绕 a (或任何其他工作共享构造)的不同线程的条件可能不同是非法的:#pragma omp for
#pragma omp parallel
{
if (...true for some threads, false for others...) // ILLEGAL!
{
#pragma omp for
for (...) ...
}
while (...true for some threads, false for others...) // ILLEGAL!
{
#pragma omp for
for (...) ...
}
}
在您的情况下,这种未指定的行为可能会导致以下事件序列:
- 每个线程都会检查条件,但可能并非所有线程都相同——有些进入
while
循环,有些则不。
- 如果他们进入
while
循环:
- 他们遇到
#pragma omp for
.
- 在 for 循环中,它们更新
error
.
- 他们在结尾处的隐式屏障处等待
#pragma omp for
。
- 如果他们不进入
while
循环:
- 他们在结尾处的隐式屏障处等待
#pragma omp parallel
。
当 OpenMP 线程到达屏障时,它会一直等待,直到其团队中的所有线程都到达屏障。的隐式屏障#pragma omp for
不适应遇到该构造的线程数。在您的情况下,某些线程将永远不会在循环结束时到达障碍for
(因为对它们来说while
条件为假)。他们跳过了while
循环,现在在#pragma omp parallel
.
结果是死锁:一些线程在结束时等待#pragma omp for
,另一些在结束时等待#pragma omp parallel
,这两个组永远不会再聚在一起......
之前在沃尔特的回答中建议的显式障碍#pragma omp for
通过分离共享变量的读取和写入来解决这个问题error
。进一步来说:
- 每个线程都检查条件,并且对所有线程都是一样的——要么全部要么没有进入
while
循环体。
- 如果他们进入
while
循环:
- 他们都在明确的障碍处等待。
- 他们都遇到了
#pragma omp for
。
- 在 for 循环中,它们更新
error
.
- 他们都在隐蔽的屏障尽头等待
#pragma omp for
。(屏障做了一个隐含的flush
,这意味着所有线程都看到 的最终值error
。)
- 返回开始。
while
循环
后:
- 他们都在隐蔽的屏障尽头等待
#pragma omp parallel
。
- 完毕。
当然,现在所有线程都执行for
循环,这并没有“最小化并行化开销”,这是您想要的。我想你必须重新构建你的代码才能达到这个目标。也许使用#pragma omp task
代替#pragma omp for
可能是一个好方法,但这取决于您的实际数据结构和算法的细节。
注意:您可以通过向 中添加nowait
子句来摆脱死锁#pragma omp for
,但这将是一种 hack,并且您的程序仍然会有未指定的行为。
1: ...或其他 OpenMP 版本中的相应部分。