您的程序有未指定的行为。请参阅OpenMP 5.0 specification1 中的第 2.8 节:
团队中的所有线程都必须遇到每个工作共享区域,或者根本不遇到
这意味着任何类型的分支(if、while 等)其条件可能因不同线程而不同围绕#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的末尾等待,这两组再也不会聚在一起了……
Walter 的回答中建议的#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 可能是一个好方法,但这取决于您的实际数据结构和算法的细节。
注意:您可以通过在 #pragma omp for 中添加 nowait 子句来摆脱死锁,但那将是一个 hack,您的程序仍然会有未指定的行为 em>.
1: ...或其他 OpenMP 版本中的相应部分。