默认情况下,nested parallelism禁用。尽管如此,您可以通过以下任一方式明确启用 nested parallelism:
omp_set_nested(1);
或者通过将OMP_NESTED 环境变量设置为true。
同样从OpenMP standard我们知道:
当一个线程遇到一个并行结构时,一组线程是
创建以执行并行区域。
遇到并行构造的线程变为
新团队的主线程,线程号为零
新平行区域的持续时间。新团队中的所有线程,
包括主线程,执行区域。一旦球队在
创建后,团队中的线程数保持不变
该平行区域的持续时间。
来自source,您可以阅读以下内容。
OpenMP 并行区域可以相互嵌套。 如果嵌套
并行性被禁用,然后由一个线程创建的新团队
在平行区域内遇到平行构造包括
只有遇到的线程。如果启用了嵌套并行,
那么新团队可能包含多个线程。
这解释了为什么当您添加第二个parallel region 时只有一个线程每个 团队执行封闭代码(即, for 循环)。换句话说,从第一个parallel region,4线程被创建,这些线程中的每一个在遇到第二个parallel region时都会创建一个新团队并成为该团队的主人(即,将拥有ID=0在新创建的团队中)。 但是,由于您没有显式启用嵌套并行性,因此每个团队仅由一个线程组成。因此,4 拥有一个线程的团队将执行 for 循环。因此,您将有以下语句:
printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
正在打印6 x 4 = 24 times(即循环迭代的总数乘以4 团队中的线程总数)。下图提供了该流程的可视化:
如果在第一个parallel region和第二个parallel region之间添加printf语句,如下:
int main() {
omp_set_num_threads(4);
#pragma omp parallel
{
printf("Before nested parallel region: I am Thread{%d}\n", omp_get_thread_num());
#pragma omp parallel for // Adding "parallel" is the cause of the problem, but I don't know how to explain it.
for (int i = 0; i < 6; i++)
{
printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
}
}
return 0;
}
您会得到类似于以下输出的内容(请记住,输出前4 行的顺序是不确定的)。
Before nested parallel region: I am Thread{1}
Before nested parallel region: I am Thread{0}
Before nested parallel region: I am Thread{2}
Before nested parallel region: I am Thread{3}
i = 0, I am Thread 0
i = 0, I am Thread 0
i = 0, I am Thread 0
(...)
i = 5, I am Thread 0
这意味着在第一个parallel region 内(但仍在第二个并行区域之外)有一个由 4 个线程组成的团队——IDs 从0 变化到3——并行执行。因此,这些线程中的每一个都将执行printf 语句:
printf("I am Thread outside the nested region {%d}\n", omp_get_thread_num());
并为omp_get_thread_num() 方法调用显示不同的值。
如前所述,嵌套并行被禁用。因此,当这些线程中的每一个遇到第二个parallel region 时,每个线程都会创建一个新团队并成为主人(即将在新创建的团队中拥有ID=0)。 - 也是该团队中唯一的成员。因此,为什么声明
printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
在循环内部,总是输出(..) I am Thread 0,因为在这个上下文中的方法omp_get_thread_num()总是返回0。然而,即使omp_get_thread_num() 方法返回了0,它并不意味着代码是按顺序执行的(由具有ID=0 的线程执行),而是每个master的4 团队正在返回他们的ID=0。
如果您启用了嵌套并行,您将获得如下图所示的流程:
为简单起见,省略了线程 1 到 3 的执行,但它与线程 0 相同。
因此,从第一个 parallel region 开始,创建了一个具有 4 线程的团队。在遇到下一个 parallel region 来自上一个团队的每个线程后,将创建一个新的团队,每个团队都有 4 线程,所以目前我们共有 16 线程跨越 4 团队。最后,每个团队将执行整个 for 循环。但是,因为您有一个#pragma omp parallel for 构造函数,所以 for 循环的迭代将在每个团队内的线程之间分配。
请记住,在上图中,我假设循环之间的迭代有一定的static 循环分布,我并不是暗示循环迭代将始终像这样在 OpenMP 标准的所有实现中划分。